From 0d3f3644df92049f4f068e27d6f6ed42f440d5ab Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 13 Mar 2015 02:58:35 +0900 Subject: [PATCH 001/396] Pass ldflags to extra_link_args mysql_config may produce link args other than -L and -l. --- setup_posix.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/setup_posix.py b/setup_posix.py index 3cb44f49..320e9e99 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -13,9 +13,6 @@ def dequote(s): s = s[1:-1] return s -def compiler_flag(f): - return "-%s" % f - def mysql_config(what): from os import popen @@ -53,29 +50,24 @@ def get_config(): libs = mysql_config("libs") client = "mysqlclient" - library_dirs = [ dequote(i[2:]) for i in libs if i.startswith(compiler_flag("L")) ] - libraries = [ dequote(i[2:]) for i in libs if i.startswith(compiler_flag("l")) ] + library_dirs = [dequote(i[2:]) for i in libs if i.startswith('-L')] + libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')] + extra_link_args = [x for x in libs if not x.startswith(('-l', '-L'))] - removable_compile_args = [ compiler_flag(f) for f in "ILl" ] - extra_compile_args = [ i.replace("%", "%%") for i in mysql_config("cflags") - if i[:2] not in removable_compile_args ] + removable_compile_args = ('-I', '-L', '-l') + extra_compile_args = [i.replace("%", "%%") for i in mysql_config("cflags") + if i[:2] not in removable_compile_args] # Copy the arch flags for linking as well - extra_link_args = list() for i in range(len(extra_compile_args)): if extra_compile_args[i] == '-arch': extra_link_args += ['-arch', extra_compile_args[i + 1]] - include_dirs = [ dequote(i[2:]) - for i in mysql_config('include') - if i.startswith(compiler_flag('I')) ] - if not include_dirs: # fix for MySQL-3.23 - include_dirs = [ dequote(i[2:]) - for i in mysql_config('cflags') - if i.startswith(compiler_flag('I')) ] + include_dirs = [dequote(i[2:]) + for i in mysql_config('include') if i.startswith('-I')] if static: - extra_objects.append(os.path.join(library_dirs[0],'lib%s.a' % client)) + extra_objects.append(os.path.join(library_dirs[0], 'lib%s.a' % client)) if client in libraries: libraries.remove(client) @@ -104,4 +96,3 @@ def get_config(): if __name__ == "__main__": sys.stderr.write("""You shouldn't be running this directly; it is used by setup.py.""") - From 009f1579c5fc708ee237f71900a0129fd2412441 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 13 Mar 2015 03:08:30 +0900 Subject: [PATCH 002/396] Update INSTALL doc --- INSTALL | 84 +++++++++------------------------------------------------ 1 file changed, 13 insertions(+), 71 deletions(-) diff --git a/INSTALL b/INSTALL index f44b410d..9caf5bcc 100644 --- a/INSTALL +++ b/INSTALL @@ -8,57 +8,25 @@ MySQLdb Installation Prerequisites ------------- -+ Python 2.3.4 or higher ++ Python 2.6, 2.7, 3.3 or higher * http://www.python.org/ - * Versions lower than 2.3 WON'T WORK. - - * 2.4 is the primary test environment. - - * Red Hat Linux: - - - Make sure you have the Python development headers and libraries - (python-devel). - + setuptools * http://pypi.python.org/pypi/setuptools -+ MySQL 3.23.32 or higher ++ MySQL 5.0 or higher * http://www.mysql.com/downloads/ - * Versions lower than 3.22 definitely WON'T WORK. - - * Versions lower than 3.22.19 might not work. - - * MySQL-3.22 might work but isn't supported anymore. It's very old. - - * MySQL-3.23 ought to work, but it's pretty elderly. - - * MySQL-4.0 is supported, but not tested and slightly discouraged. - - * MySQL-4.1 is supported. The prepared statements API is not - supported, and won't be until MySQLdb-1.3 or 2.0, if ever. + * MySQL-4.0 and MySQL-4.1 may work, but not supported. * MySQL-5.0 is supported and tested, including stored procedures. * MySQL-5.1 is supported (currently a release candidate) but untested. It should work. - * MySQL-6.0 is sorta-kinda-supported (currently alpha) but untested. - It should work. - - * Drizzle is a fork of MySQL. So far - the C API looks really similar except everything is renamed. - Drizzle support probably won't happen in 1.2. There may be have to - be an entirely different module, but still using DB-API. - - * MaxDB, formerly known as SAP DB (and maybe Adabas D?), is a - completely different animal. Use the sapdb.sql module that comes - with MaxDB. - * Red Hat Linux packages: - mysql-devel to compile @@ -75,27 +43,6 @@ Prerequisites - MySQL-shared to run if you compiled with MySQL-shared installed - * Transactions (particularly InnoDB tables) are supported for - MySQL-3.23 and up. You may need a special package from your vendor - with this support turned on. - -+ zlib - - * Required for MySQL-3.23 and newer. - - * Red Hat Linux - - - zlib-devel to compile - - - zlib to run - -+ openssl - - * May be needed for MySQL-4.0 or newer, depending on compilation - options. If you need it, you probably already have it. - - - you may need openssl-devel on some platforms - + C compiler * Most free software-based systems already have this, usually gcc. @@ -103,10 +50,8 @@ Prerequisites * Most commercial UNIX platforms also come with a C compiler, or you can also use gcc. - * If you have some Windows flavor, you usually have to pay extra - for this, or you can use Cygwin_. - -.. _Cygwin: http://www.cygwin.com/ + * If you have some Windows flavor, you should use Windows SDK or + Visual C++. Building and installing @@ -134,12 +79,18 @@ edit the [options] section of site.cfg: if True, try to link against a static library; otherwise link against dynamic libraries (default). You may need static linking to use the embedded server. + This option doesn't work for MySQL>5.6 since libmysqlclient + requires libstdc++. If you want to use, add `-lstdc++` to + mysql_config manually. +If `/lib` is not added to `/etc/ld.so.conf`, `import _mysql` +doesn't work. To fix this, (1) set `LD_LIBRARY_PATH`, or (2) add +`-Wl,-rpath,/lib` to ldflags in your mysql_config. Finally, putting it together:: - $ tar xfz MySQL-python-1.2.1.tar.gz - $ cd MySQL-python-1.2.1 + $ tar xz mysqlclient-1.3.6.tar.gz + $ cd mysqlclient-1.3.6 $ # edit site.cfg if necessary $ python setup.py build $ sudo python setup.py install # or su first @@ -168,15 +119,6 @@ On Windows, you will definitely have to edit site.cfg since there is no mysql_config in the MySQL package. -Zope -.... - -If you are using a binary package of Zope, you need run setup.py with -the python executable that came with Zope. Otherwise, you'll install -into the wrong Python tree and Zope (ZMySQLDA) will not be able to -find _mysql. - - Binary Packages --------------- From 1383f3ff4214daa15d69946f1f608937243d1671 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 11 Oct 2015 16:42:08 +0900 Subject: [PATCH 003/396] Add GPL v2 LICENSE file --- LICENSE | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + 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. From 8c090157b7031de40a8f177aadf2507e56f602f5 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 11 Oct 2015 20:09:17 +0900 Subject: [PATCH 004/396] Check after mysql_store_result propery fixes #51 --- _mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 5d3f572e..62e24015 100644 --- a/_mysql.c +++ b/_mysql.c @@ -408,7 +408,7 @@ _mysql_ResultObject_Initialize( self->result = result; Py_END_ALLOW_THREADS ; if (!result) { - if (mysql_field_count(&(conn->connection)) > 0) { + if (mysql_errno(&(conn->connection))) { _mysql_Exception(conn); return -1; } From b07937487dc10c818e1ae66329fa03a9c5b9ff21 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 12 Oct 2015 02:29:06 +0900 Subject: [PATCH 005/396] Don't fetch warnings when there is next result Fixes #48 --- MySQLdb/cursors.py | 18 ++++++++++++------ _mysql.c | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index b1b6952a..355cfd44 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -109,6 +109,14 @@ def _check_executed(self): def _warning_check(self): from warnings import warn if self._warnings: + # When there is next result, fetching warnings cause "command + # out of sync" error. + if self._result.has_next: + msg = "There are %d MySQL warnings." % (self._warnings,) + self.messages.append(msg) + warn(msg, self.Warning, 3) + return + warnings = self._get_db().show_warnings() if warnings: # This is done in two loops in case @@ -204,23 +212,21 @@ def execute(self, query, args=None): if isinstance(query, unicode): query = query.encode(db.unicode_literal.charset, 'surrogateescape') + res = None try: - r = None - r = self._query(query) + res = self._query(query) except TypeError as m: if m.args[0] in ("not enough arguments for format string", "not all arguments converted"): self.errorhandler(self, ProgrammingError, m.args[0]) else: self.errorhandler(self, TypeError, m) - except (SystemExit, KeyboardInterrupt): - raise - except: + except Exception: exc, value = sys.exc_info()[:2] self.errorhandler(self, exc, value) self._executed = query if not self._defer_warnings: self._warning_check() - return r + return res def executemany(self, query, args): """Execute a multi-row query. diff --git a/_mysql.c b/_mysql.c index 62e24015..ae2e3a0e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -88,6 +88,7 @@ typedef struct { MYSQL_RES *result; int nfields; int use; + char has_next; PyObject *converter; } _mysql_ResultObject; @@ -406,6 +407,7 @@ _mysql_ResultObject_Initialize( else result = mysql_store_result(&(conn->connection)); self->result = result; + self->has_next = (char)mysql_more_results(&(conn->connection)); Py_END_ALLOW_THREADS ; if (!result) { if (mysql_errno(&(conn->connection))) { @@ -1520,7 +1522,7 @@ _mysql__fetch_row( int maxrows, _PYFUNC *convert_row) { - unsigned int i; + int i; MYSQL_ROW row; for (i = skiprows; i<(skiprows+maxrows); i++) { @@ -1573,14 +1575,14 @@ _mysql_ResultObject_fetch_row( _mysql_row_to_dict_old }; _PYFUNC *convert_row; - unsigned int maxrows=1, how=0, skiprows=0, rowsadded; + int maxrows=1, how=0, skiprows=0, rowsadded; PyObject *r=NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, &maxrows, &how)) return NULL; check_result_connection(self); - if (how >= sizeof(row_converters)) { + if (how >= (int)sizeof(row_converters)) { PyErr_SetString(PyExc_ValueError, "how out of range"); return NULL; } @@ -2653,6 +2655,13 @@ static struct PyMemberDef _mysql_ResultObject_memberlist[] = { READONLY, "Type conversion mapping" }, + { + "has_next", + T_BOOL, + offsetof(_mysql_ResultObject, has_next), + READONLY, + "Has next result" + }, {NULL} /* Sentinel */ }; From 98bec5fb8ce5ff34373f1ab48090fc717a7e06ac Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 5 Nov 2015 03:38:13 +0900 Subject: [PATCH 006/396] fixup --- MySQLdb/cursors.py | 2 +- _mysql.c | 56 ++-- tests/configdb.py | 5 +- tests/test_MySQLdb_dbapi20.py | 409 +++++++++++++++--------------- tests/test_MySQLdb_nonstandard.py | 3 +- 5 files changed, 238 insertions(+), 237 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 355cfd44..e2d74af1 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -111,7 +111,7 @@ def _warning_check(self): if self._warnings: # When there is next result, fetching warnings cause "command # out of sync" error. - if self._result.has_next: + if self._result and self._result.has_next: msg = "There are %d MySQL warnings." % (self._warnings,) self.messages.append(msg) warn(msg, self.Warning, 3) diff --git a/_mysql.c b/_mysql.c index ae2e3a0e..bcd15b84 100644 --- a/_mysql.c +++ b/_mysql.c @@ -68,7 +68,7 @@ static PyObject *_mysql_IntegrityError; static PyObject *_mysql_InternalError; static PyObject *_mysql_ProgrammingError; static PyObject *_mysql_NotSupportedError; - + typedef struct { PyObject_HEAD MYSQL connection; @@ -227,7 +227,7 @@ _mysql_Exception(_mysql_ConnectionObject *c) Py_DECREF(t); return NULL; } - + static char _mysql_server_init__doc__[] = "Initialize embedded server. If this client is not linked against\n\ the embedded server library, this function does nothing.\n\ @@ -250,7 +250,7 @@ static PyObject *_mysql_server_init( "already initialized"); return NULL; } - + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", kwlist, &cmd_args, &groups)) return NULL; @@ -349,7 +349,7 @@ static PyObject *_mysql_server_end( } return _mysql_Exception(NULL); } - + #if MYSQL_VERSION_ID >= 32314 static char _mysql_thread_safe__doc__[] = "Indicates whether the client is compiled as thread-safe."; @@ -557,7 +557,7 @@ _mysql_ConnectionObject_Initialize( char *init_command=NULL, *read_default_file=NULL, *read_default_group=NULL; - + self->converter = NULL; self->open = 0; check_server_init(-1); @@ -741,7 +741,7 @@ _mysql_connect( PyObject *kwargs) { _mysql_ConnectionObject *c=NULL; - + c = MyAlloc(_mysql_ConnectionObject, _mysql_ConnectionObject_Type); if (c == NULL) return NULL; if (_mysql_ConnectionObject_Initialize(c, args, kwargs)) { @@ -1291,7 +1291,7 @@ _mysql_escape_dict( Py_XDECREF(r); return NULL; } - + static char _mysql_ResultObject_describe__doc__[] = "Returns the sequence of 7-tuples required by the DB-API for\n\ the Cursor.description attribute.\n\ @@ -1328,7 +1328,7 @@ _mysql_ResultObject_describe( Py_XDECREF(d); return NULL; } - + static char _mysql_ResultObject_field_flags__doc__[] = "Returns a tuple of field flags, one for each column in the result.\n\ " ; @@ -1736,7 +1736,7 @@ _mysql_ConnectionObject_get_character_set_info( { PyObject *result; MY_CHARSET_INFO cs; - + if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); mysql_get_character_set_info(&(self->connection), &cs); @@ -2726,44 +2726,44 @@ PyTypeObject _mysql_ConnectionObject_Type = { 0, /* tp_setattr */ 0, /*tp_compare*/ (reprfunc)_mysql_ConnectionObject_repr, /* tp_repr */ - + /* Method suites for standard classes */ - + 0, /* (PyNumberMethods *) tp_as_number */ 0, /* (PySequenceMethods *) tp_as_sequence */ 0, /* (PyMappingMethods *) tp_as_mapping */ - + /* More standard operations (here for binary compatibility) */ - + 0, /* (hashfunc) tp_hash */ 0, /* (ternaryfunc) tp_call */ 0, /* (reprfunc) tp_str */ (getattrofunc)_mysql_ConnectionObject_getattro, /* tp_getattro */ (setattrofunc)_mysql_ConnectionObject_setattro, /* tp_setattro */ - + /* Functions to access object as input/output buffer */ 0, /* (PyBufferProcs *) tp_as_buffer */ - + /* (tp_flags) Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, _mysql_connect__doc__, /* (char *) tp_doc Documentation string */ /* call function for all accessible objects */ (traverseproc) _mysql_ConnectionObject_traverse, /* tp_traverse */ - + /* delete references to contained objects */ (inquiry) _mysql_ConnectionObject_clear, /* tp_clear */ /* rich comparisons */ 0, /* (richcmpfunc) tp_richcompare */ - + /* weak reference enabler */ 0, /* (long) tp_weaklistoffset */ /* Iterators */ 0, /* (getiterfunc) tp_iter */ 0, /* (iternextfunc) tp_iternext */ - + /* Attribute descriptor and subclassing stuff */ (struct PyMethodDef *)_mysql_ConnectionObject_methods, /* tp_methods */ (struct PyMemberDef *)_mysql_ConnectionObject_memberlist, /* tp_members */ @@ -2798,45 +2798,45 @@ PyTypeObject _mysql_ResultObject_Type = { 0, /* tp_setattr */ 0, /*tp_compare*/ (reprfunc)_mysql_ResultObject_repr, /* tp_repr */ - + /* Method suites for standard classes */ - + 0, /* (PyNumberMethods *) tp_as_number */ 0, /* (PySequenceMethods *) tp_as_sequence */ 0, /* (PyMappingMethods *) tp_as_mapping */ - + /* More standard operations (here for binary compatibility) */ - + 0, /* (hashfunc) tp_hash */ 0, /* (ternaryfunc) tp_call */ 0, /* (reprfunc) tp_str */ (getattrofunc)PyObject_GenericGetAttr, /* tp_getattro */ (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ - + /* Functions to access object as input/output buffer */ 0, /* (PyBufferProcs *) tp_as_buffer */ - + /* Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, - + _mysql_ResultObject__doc__, /* (char *) tp_doc Documentation string */ /* call function for all accessible objects */ (traverseproc) _mysql_ResultObject_traverse, /* tp_traverse */ - + /* delete references to contained objects */ (inquiry) _mysql_ResultObject_clear, /* tp_clear */ /* rich comparisons */ 0, /* (richcmpfunc) tp_richcompare */ - + /* weak reference enabler */ 0, /* (long) tp_weaklistoffset */ /* Iterators */ 0, /* (getiterfunc) tp_iter */ 0, /* (iternextfunc) tp_iternext */ - + /* Attribute descriptor and subclassing stuff */ (struct PyMethodDef *) _mysql_ResultObject_methods, /* tp_methods */ (struct PyMemberDef *) _mysql_ResultObject_memberlist, /*tp_members */ diff --git a/tests/configdb.py b/tests/configdb.py index cd6d43da..307cc3f4 100644 --- a/tests/configdb.py +++ b/tests/configdb.py @@ -10,16 +10,15 @@ read_default_group = "MySQLdb-tests", ) + def connection_kwargs(kwargs): db_kwargs = connect_kwargs.copy() db_kwargs.update(kwargs) return db_kwargs + def connection_factory(**kwargs): import MySQLdb db_kwargs = connection_kwargs(kwargs) db = MySQLdb.connect(**db_kwargs) return db - - - diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 2832e323..d8598dd3 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -1,204 +1,205 @@ -#!/usr/bin/env python -import dbapi20 -import unittest -import MySQLdb -from configdb import connection_kwargs -import warnings -warnings.simplefilter("ignore") - -class test_MySQLdb(dbapi20.DatabaseAPI20Test): - driver = MySQLdb - connect_args = () - connect_kw_args = connection_kwargs(dict(sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")) - - def test_setoutputsize(self): pass - def test_setoutputsize_basic(self): pass - def test_nextset(self): pass - - """The tests on fetchone and fetchall and rowcount bogusly - test for an exception if the statement cannot return a - result set. MySQL always returns a result set; it's just that - some things return empty result sets.""" - - def test_fetchall(self): - con = self._connect() - try: - cur = con.cursor() - # cursor.fetchall should raise an Error if called - # without executing a query that may return rows (such - # as a select) - self.assertRaises(self.driver.Error, cur.fetchall) - - self.executeDDL1(cur) - for sql in self._populate(): - cur.execute(sql) - - # cursor.fetchall should raise an Error if called - # after executing a a statement that cannot return rows -## self.assertRaises(self.driver.Error,cur.fetchall) - - cur.execute('select name from %sbooze' % self.table_prefix) - rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,len(self.samples))) - self.assertEqual(len(rows),len(self.samples), - 'cursor.fetchall did not retrieve all rows' - ) - rows = [r[0] for r in rows] - rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'cursor.fetchall retrieved incorrect rows' - ) - rows = cur.fetchall() - self.assertEqual( - len(rows),0, - 'cursor.fetchall should return an empty list if called ' - 'after the whole result set has been fetched' - ) - self.assertTrue(cur.rowcount in (-1,len(self.samples))) - - self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) - rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,0)) - self.assertEqual(len(rows),0, - 'cursor.fetchall should return an empty list if ' - 'a select query returns no rows' - ) - - finally: - con.close() - - def test_fetchone(self): - con = self._connect() - try: - cur = con.cursor() - - # cursor.fetchone should raise an Error if called before - # executing a select-type query - self.assertRaises(self.driver.Error,cur.fetchone) - - # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows - self.executeDDL1(cur) -## self.assertRaises(self.driver.Error,cur.fetchone) - - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if a query retrieves ' - 'no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) - - # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) -## self.assertRaises(self.driver.Error,cur.fetchone) - - cur.execute('select name from %sbooze' % self.table_prefix) - r = cur.fetchone() - self.assertEqual(len(r),1, - 'cursor.fetchone should have retrieved a single row' - ) - self.assertEqual(r[0],'Victoria Bitter', - 'cursor.fetchone retrieved incorrect data' - ) -## self.assertEqual(cur.fetchone(),None, -## 'cursor.fetchone should return None if no more rows available' -## ) - self.assertTrue(cur.rowcount in (-1,1)) - finally: - con.close() - - # Same complaint as for fetchall and fetchone - def test_rowcount(self): - con = self._connect() - try: - cur = con.cursor() - self.executeDDL1(cur) -## self.assertEqual(cur.rowcount,-1, -## 'cursor.rowcount should be -1 after executing no-result ' -## 'statements' -## ) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) -## self.assertTrue(cur.rowcount in (-1,1), -## 'cursor.rowcount should == number or rows inserted, or ' -## 'set to -1 after executing an insert statement' -## ) - cur.execute("select name from %sbooze" % self.table_prefix) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number of rows returned, or ' - 'set to -1 after executing a select statement' - ) - self.executeDDL2(cur) -## self.assertEqual(cur.rowcount,-1, -## 'cursor.rowcount not being reset to -1 after executing ' -## 'no-result statements' -## ) - finally: - con.close() - - def test_callproc(self): - pass # performed in test_MySQL_capabilities - - def help_nextset_setUp(self,cur): - ''' Should create a procedure called deleteme - that returns two result sets, first the - number of rows in booze then "name from booze" - ''' - sql=""" - create procedure deleteme() - begin - select count(*) from %(tp)sbooze; - select name from %(tp)sbooze; - end - """ % dict(tp=self.table_prefix) - cur.execute(sql) - - def help_nextset_tearDown(self,cur): - 'If cleaning up is needed after nextSetTest' - cur.execute("drop procedure deleteme") - - def test_nextset(self): - from warnings import warn - con = self._connect() - try: - cur = con.cursor() - if not hasattr(cur,'nextset'): - return - - try: - self.executeDDL1(cur) - sql=self._populate() - for sql in self._populate(): - cur.execute(sql) - - self.help_nextset_setUp(cur) - - cur.callproc('deleteme') - numberofrows=cur.fetchone() - assert numberofrows[0]== len(self.samples) - assert cur.nextset() - names=cur.fetchall() - assert len(names) == len(self.samples) - s=cur.nextset() - if s: - empty = cur.fetchall() - self.assertEquals(len(empty), 0, - "non-empty result set after other result sets") - #warn("Incompatibility: MySQL returns an empty result set for the CALL itself", - # Warning) - #assert s == None,'No more return sets, should return None' - finally: - self.help_nextset_tearDown(cur) - - finally: - con.close() - - -if __name__ == '__main__': - unittest.main() +#!/usr/bin/env python +import dbapi20 +import unittest +import MySQLdb +from configdb import connection_kwargs +import warnings +warnings.simplefilter("ignore") + + +class test_MySQLdb(dbapi20.DatabaseAPI20Test): + driver = MySQLdb + connect_args = () + connect_kw_args = connection_kwargs(dict(sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")) + + def test_setoutputsize(self): pass + def test_setoutputsize_basic(self): pass + def test_nextset(self): pass + + """The tests on fetchone and fetchall and rowcount bogusly + test for an exception if the statement cannot return a + result set. MySQL always returns a result set; it's just that + some things return empty result sets.""" + + def test_fetchall(self): + con = self._connect() + try: + cur = con.cursor() + # cursor.fetchall should raise an Error if called + # without executing a query that may return rows (such + # as a select) + self.assertRaises(self.driver.Error, cur.fetchall) + + self.executeDDL1(cur) + for sql in self._populate(): + cur.execute(sql) + + # cursor.fetchall should raise an Error if called + # after executing a a statement that cannot return rows + #self.assertRaises(self.driver.Error,cur.fetchall) + + cur.execute('select name from %sbooze' % self.table_prefix) + rows = cur.fetchall() + self.assertTrue(cur.rowcount in (-1,len(self.samples))) + self.assertEqual(len(rows),len(self.samples), + 'cursor.fetchall did not retrieve all rows' + ) + rows = [r[0] for r in rows] + rows.sort() + for i in range(0,len(self.samples)): + self.assertEqual(rows[i],self.samples[i], + 'cursor.fetchall retrieved incorrect rows' + ) + rows = cur.fetchall() + self.assertEqual( + len(rows),0, + 'cursor.fetchall should return an empty list if called ' + 'after the whole result set has been fetched' + ) + self.assertTrue(cur.rowcount in (-1,len(self.samples))) + + self.executeDDL2(cur) + cur.execute('select name from %sbarflys' % self.table_prefix) + rows = cur.fetchall() + self.assertTrue(cur.rowcount in (-1,0)) + self.assertEqual(len(rows),0, + 'cursor.fetchall should return an empty list if ' + 'a select query returns no rows' + ) + + finally: + con.close() + + def test_fetchone(self): + con = self._connect() + try: + cur = con.cursor() + + # cursor.fetchone should raise an Error if called before + # executing a select-type query + self.assertRaises(self.driver.Error,cur.fetchone) + + # cursor.fetchone should raise an Error if called after + # executing a query that cannnot return rows + self.executeDDL1(cur) +## self.assertRaises(self.driver.Error,cur.fetchone) + + cur.execute('select name from %sbooze' % self.table_prefix) + self.assertEqual(cur.fetchone(),None, + 'cursor.fetchone should return None if a query retrieves ' + 'no rows' + ) + self.assertTrue(cur.rowcount in (-1,0)) + + # cursor.fetchone should raise an Error if called after + # executing a query that cannnot return rows + cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( + self.table_prefix + )) +## self.assertRaises(self.driver.Error,cur.fetchone) + + cur.execute('select name from %sbooze' % self.table_prefix) + r = cur.fetchone() + self.assertEqual(len(r),1, + 'cursor.fetchone should have retrieved a single row' + ) + self.assertEqual(r[0],'Victoria Bitter', + 'cursor.fetchone retrieved incorrect data' + ) +## self.assertEqual(cur.fetchone(),None, +## 'cursor.fetchone should return None if no more rows available' +## ) + self.assertTrue(cur.rowcount in (-1,1)) + finally: + con.close() + + # Same complaint as for fetchall and fetchone + def test_rowcount(self): + con = self._connect() + try: + cur = con.cursor() + self.executeDDL1(cur) +## self.assertEqual(cur.rowcount,-1, +## 'cursor.rowcount should be -1 after executing no-result ' +## 'statements' +## ) + cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( + self.table_prefix + )) +## self.assertTrue(cur.rowcount in (-1,1), +## 'cursor.rowcount should == number or rows inserted, or ' +## 'set to -1 after executing an insert statement' +## ) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertTrue(cur.rowcount in (-1,1), + 'cursor.rowcount should == number of rows returned, or ' + 'set to -1 after executing a select statement' + ) + self.executeDDL2(cur) +## self.assertEqual(cur.rowcount,-1, +## 'cursor.rowcount not being reset to -1 after executing ' +## 'no-result statements' +## ) + finally: + con.close() + + def test_callproc(self): + pass # performed in test_MySQL_capabilities + + def help_nextset_setUp(self,cur): + ''' Should create a procedure called deleteme + that returns two result sets, first the + number of rows in booze then "name from booze" + ''' + sql=""" + create procedure deleteme() + begin + select count(*) from %(tp)sbooze; + select name from %(tp)sbooze; + end + """ % dict(tp=self.table_prefix) + cur.execute(sql) + + def help_nextset_tearDown(self,cur): + 'If cleaning up is needed after nextSetTest' + cur.execute("drop procedure deleteme") + + def test_nextset(self): + #from warnings import warn + con = self._connect() + try: + cur = con.cursor() + if not hasattr(cur, 'nextset'): + return + + try: + self.executeDDL1(cur) + sql=self._populate() + for sql in self._populate(): + cur.execute(sql) + + self.help_nextset_setUp(cur) + + cur.callproc('deleteme') + numberofrows=cur.fetchone() + assert numberofrows[0]== len(self.samples) + assert cur.nextset() + names=cur.fetchall() + assert len(names) == len(self.samples) + s=cur.nextset() + if s: + empty = cur.fetchall() + self.assertEquals(len(empty), 0, + "non-empty result set after other result sets") + #warn("Incompatibility: MySQL returns an empty result set for the CALL itself", + # Warning) + #assert s == None,'No more return sets, should return None' + finally: + self.help_nextset_tearDown(cur) + + finally: + con.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index d92b260a..2ca5a544 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -7,6 +7,7 @@ import warnings warnings.simplefilter("ignore") + class TestDBAPISet(unittest.TestCase): def test_set_equality(self): self.assertTrue(MySQLdb.STRING == MySQLdb.STRING) @@ -21,7 +22,7 @@ def test_set_inequality_membership(self): self.assertTrue(FIELD_TYPE.DATE != MySQLdb.STRING) -class CoreModule(unittest.TestCase): +class TestCoreModule(unittest.TestCase): """Core _mysql module features.""" def test_NULL(self): From c66f02532b6e73714853e161126ab83471722a64 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 5 Nov 2015 03:40:23 +0900 Subject: [PATCH 007/396] tox: Add py35 --- .travis.yml | 6 ++++++ tox.ini | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dab1f60f..e58e651e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ sudo: false language: python +python: "3.5" + cache: pip install: - pip install tox @@ -8,4 +10,8 @@ before_script: - "mysql --help" - "mysql --print-defaults" - "mysql -e 'create database mysqldb_test charset utf8mb4;'" + script: TESTDB=travis.cnf tox + + +# vim: sw=4 ts=4 sts=4 diff --git a/tox.ini b/tox.ini index 708221fb..4c7fe5fd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27, pypy, py33, py34 +envlist = py26, py27, pypy, py33, py34, py35 [testenv] passenv = TESTDB From ff3a99b9458b7ea395f54671bdfafcc72389e157 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 5 Nov 2015 04:00:04 +0900 Subject: [PATCH 008/396] update metadate for 1.3.7 --- metadata.cfg | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index f50c0fa0..3f6d5d1b 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,13 +1,13 @@ [metadata] -version: 1.3.6 -version_info: (1,3,6,'final',1) +version: 1.3.7 +version_info: (1,3,7,'final',1) description: Python interface to MySQL long_description: ========================= Python interface to MySQL ========================= \n - mysqlclient is a fork of MySQL-python. It adds Python 3.3 support + mysqlclient is a fork of MySQL-python. It adds Python 3.3~ support and merges some pull requests. \n MySQLdb is an interface to the popular MySQL_ database server for @@ -17,7 +17,7 @@ long_description: - Thread-safety - Thread-friendliness (threads will not block each other) \n - MySQL-3.23 through 5.5 and Python-2.7, 3.3-3.4 are currently + MySQL-4.1 through 5.5 and Python-2.7, 3.3-3.5 are currently supported. PyPy is supported. \n MySQLdb is `Free Software`_. @@ -49,6 +49,7 @@ classifiers: Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: From e98e73757a699f175c098749d08cc3c54e690948 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 5 Nov 2015 04:10:44 +0900 Subject: [PATCH 009/396] Update HISTORY --- HISTORY | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/HISTORY b/HISTORY index 58ff35e1..b283d5c6 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,24 @@ +===================== + What's new in 1.3.7 +===================== + +Support link args other than '-L' and '-l' from mysql_config. + +Missing value for column without default value cause IntegrityError. (#33) + +Support BIT type. (#38) + +More tests for date and time columns. (#41) + +Fix calling .execute() method for closed cursor cause TypeError. (#37) + +Improve peformance to parse date. (#43) + +Support geometry types (#49) + +Fix warning while multi statement cause ProgrammingError. (#48) + + ===================== What's new in 1.3.6 ===================== From 4b0519a0c41f7776bf45b3f7a35b110784706e7c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 5 Nov 2015 04:10:57 +0900 Subject: [PATCH 010/396] dos2unix HISTORY --- HISTORY | 552 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 276 insertions(+), 276 deletions(-) diff --git a/HISTORY b/HISTORY index b283d5c6..97ffe20d 100644 --- a/HISTORY +++ b/HISTORY @@ -1,276 +1,276 @@ -===================== - What's new in 1.3.7 -===================== - -Support link args other than '-L' and '-l' from mysql_config. - -Missing value for column without default value cause IntegrityError. (#33) - -Support BIT type. (#38) - -More tests for date and time columns. (#41) - -Fix calling .execute() method for closed cursor cause TypeError. (#37) - -Improve peformance to parse date. (#43) - -Support geometry types (#49) - -Fix warning while multi statement cause ProgrammingError. (#48) - - -===================== - What's new in 1.3.6 -===================== - -Fix escape_string() doesn't work. - -Remove `Cursor.__del__` to fix uncollectable circular reference on Python 3.3. - -Add context manager support to `Cursor`. It automatically closes cursor on `__exit__`. - -.. code-block:: - - with conn.cursor() as cur: - cur.execute("SELECT 1+1") - print(cur.fetchone()) - # cur is now closed - - -===================== - What's new in 1.3.5 -===================== - -Fix TINYBLOB, MEDIUMBLOB and LONGBLOB are treated as string and decoded -to unicode or cause UnicodeError. - -Fix aware datetime is formatted with timezone offset (e.g. "+0900"). - - -===================== - What's new in 1.3.4 -===================== - -* Remove compiler warnings. -* Fix compile error when using libmariadbclient. -* Fix GIL deadlock while dealloc. - -===================== - What's new in 1.3.3 -===================== - -* Fix exception reraising doesn't work. - -===================== - What's new in 1.3.2 -===================== - -* Add send_query() and read_query_result() method to low level connection. -* Add waiter option. - - -===================== - What's new in 1.3.1 -===================== - -This is a first fork of MySQL-python. -Now named "mysqlclient" - -* Support Python 3 -* Add autocommit option -* Support microsecond in datetime field. - - -===================== - What's new in 1.2.4 -===================== - -final -===== - -No changes. - - -rc 1 -==== - -Fixed a dangling reference to the old types module. - - -beta 5 -====== - -Another internal fix for handling remapped character sets. - -_mysql.c was broken for the case where read_timeout was *not* available. (Issue #6) - -Documentation was converted to sphinx but there is a lot of cleanup left to do. - - -beta 4 -====== - -Added support for the MySQL read_timeout option. Contributed by -Jean Schurger (jean@schurger.org). - -Added a workaround so that the MySQL character set utf8mb4 works with Python; utf8 is substituted -on the Python side. - - -beta 3 -====== - -Unified test database configuration, and set up CI testing with Travis. - -Applied several patches from AndrĂ© Malo (ndparker@users.sf.net) which fix some issues -with exception handling and reference counting and TEXT/BLOB conversion. - - -beta 2 -====== - -Reverted an accidental change in the exception format. (issue #1) - -Reverted some raise statements so that they will continue to work with Python < 2.6 - - -beta 1 -====== - -A lot of work has been done towards Python 3 compatibility, and avoiding warnings with Python 2.7. -This includes import changes, converting dict.has_kay(k) to k in dict, updating some test suite methods, etc. - -Due to the difficulties of supporting Python 3 and Python < 2.7, 1.2.4 will support Python 2.4 though 2.7. -1.3.0 will support Python 3 and Python 2.7 and 2.6. - -MySQLdb-2.0 is instead going to become moist-1.0. See https://github.com/farcepest/moist - -The Windows build has been simplified, and I plan to correct pre-built i386 packages built -against the python.org Python-2.7 package and MySQL Connector/C-6.0. Contact me if you -need ia64 packages. - -The connection's cursorclass (if not default) was being lost on reconnect. - -Newer versions of MySQL don't use OpenSSL and therefore don't have HAVE_SSL defined, but they do have -a different SSL library. Fixed this so SSL support would be enabled in this case. - -The regex that looked for SQL INSERT statement and VALUES in cursor.executemany() was made case-insensitive -again. - - -===================== - What's new in 1.2.3 -===================== - -ez_setup.py has been update to include various fixes that affect the build. - -Better Python version and dependency detection as well as eliminate exception -warnings under Python 2.6. - -Eliminated memory leaks related to Unicode and failed connections. - -Corrected connection .escape() functionality. - -Miscellaneous cleanups and and expanded testing suite to ensure ongoing release -quality. - -===================== - What's new in 1.2.2 -===================== - -The build system has been completely redone and should now build -on Windows without any patching; uses setuptools. - -Added compatibility for Python 2.5, including support for with statement. - -connection.ping() now takes an optional boolean argument which can -enable (or disable) automatic reconnection. - -Support returning SET columns as Python sets was removed due to an -API bug in MySQL; corresponding test removed. - -Added a test for single-character CHAR columns. - -BLOB columns are now returned as Python strings instead of byte arrays. - -BINARY character columns are always returned as Python strings, and not -unicode. - -Fixed a bug introduced in 1.2.1 where the new SHOW WARNINGS support broke -SSCursor. - -Only encode the query (convert to a string) when it is a unicode instance; -re-encoding encoded strings would break things. - -Make a deep copy of conv when connecting, since it can be modified. - -Added support for new VARCHAR and BIT column types. - -DBAPISet objects were broken, but nobody noticed. - - -======================== - What's new in 1.2.1_p2 -======================== - -There are some minor build fixes which probably only affect MySQL -older than 4.0. - -If you had MySQL older than 4.1, the new charset and sql_mode -parameters didn't work right. In fact, it was impossible to create -a connection due to the charset problem. - -If you are using MySQL-4.1 or newer, there is no practical difference -between 1.2.1 and 1.2.1_p2, and you don't need to upgrade. - - -===================== - What's new in 1.2.1 -===================== - -Switched to Subversion. Was going to do this for 1.3, but a -SourceForge CVS outage has forced the issue. - -Mapped a lot of new 4.1 and 5.0 error codes to Python exceptions - -Added an API call for mysql_set_character_set(charset) (MySQL > 5.0.7) - -Added an API call for mysql_get_character_set_info() (MySQL > 5.0.10) - -Revamped the build system. Edit site.cfg if necessary (probably not -in most cases) - -Python-2.3 is now the minimum version. - -Dropped support for mx.Datetime and stringtimes; always uses Python -datetime module now. - -Improved unit tests - -New connect() options: -* charset: sets character set, implies use_unicode -* sql_mode: sets SQL mode (i.e. ANSI, etc.; see MySQL docs) - -When using MySQL-4.1 or newer, enables MULTI_STATEMENTS - -When using MySQL-5.0 or newer, enables MULTI_RESULTS - -When using MySQL-4.1 or newer, more detailed warning messages -are produced - -SET columns returned as Python Set types; you can pass a Set as -a parameter to cursor.execute(). - -Support for the new MySQL-5.0 DECIMAL implementation - -Support for Python Decimal type - -Some use of weak references internally. Cursors no longer leak -if you don't close them. Connections still do, unfortunately. - -ursor.fetchXXXDict() methods raise DeprecationWarning - -cursor.begin() is making a brief reappearence. - -cursor.callproc() now works, with some limitations. - +===================== + What's new in 1.3.7 +===================== + +Support link args other than '-L' and '-l' from mysql_config. + +Missing value for column without default value cause IntegrityError. (#33) + +Support BIT type. (#38) + +More tests for date and time columns. (#41) + +Fix calling .execute() method for closed cursor cause TypeError. (#37) + +Improve peformance to parse date. (#43) + +Support geometry types (#49) + +Fix warning while multi statement cause ProgrammingError. (#48) + + +===================== + What's new in 1.3.6 +===================== + +Fix escape_string() doesn't work. + +Remove `Cursor.__del__` to fix uncollectable circular reference on Python 3.3. + +Add context manager support to `Cursor`. It automatically closes cursor on `__exit__`. + +.. code-block:: + + with conn.cursor() as cur: + cur.execute("SELECT 1+1") + print(cur.fetchone()) + # cur is now closed + + +===================== + What's new in 1.3.5 +===================== + +Fix TINYBLOB, MEDIUMBLOB and LONGBLOB are treated as string and decoded +to unicode or cause UnicodeError. + +Fix aware datetime is formatted with timezone offset (e.g. "+0900"). + + +===================== + What's new in 1.3.4 +===================== + +* Remove compiler warnings. +* Fix compile error when using libmariadbclient. +* Fix GIL deadlock while dealloc. + +===================== + What's new in 1.3.3 +===================== + +* Fix exception reraising doesn't work. + +===================== + What's new in 1.3.2 +===================== + +* Add send_query() and read_query_result() method to low level connection. +* Add waiter option. + + +===================== + What's new in 1.3.1 +===================== + +This is a first fork of MySQL-python. +Now named "mysqlclient" + +* Support Python 3 +* Add autocommit option +* Support microsecond in datetime field. + + +===================== + What's new in 1.2.4 +===================== + +final +===== + +No changes. + + +rc 1 +==== + +Fixed a dangling reference to the old types module. + + +beta 5 +====== + +Another internal fix for handling remapped character sets. + +_mysql.c was broken for the case where read_timeout was *not* available. (Issue #6) + +Documentation was converted to sphinx but there is a lot of cleanup left to do. + + +beta 4 +====== + +Added support for the MySQL read_timeout option. Contributed by +Jean Schurger (jean@schurger.org). + +Added a workaround so that the MySQL character set utf8mb4 works with Python; utf8 is substituted +on the Python side. + + +beta 3 +====== + +Unified test database configuration, and set up CI testing with Travis. + +Applied several patches from AndrĂ© Malo (ndparker@users.sf.net) which fix some issues +with exception handling and reference counting and TEXT/BLOB conversion. + + +beta 2 +====== + +Reverted an accidental change in the exception format. (issue #1) + +Reverted some raise statements so that they will continue to work with Python < 2.6 + + +beta 1 +====== + +A lot of work has been done towards Python 3 compatibility, and avoiding warnings with Python 2.7. +This includes import changes, converting dict.has_kay(k) to k in dict, updating some test suite methods, etc. + +Due to the difficulties of supporting Python 3 and Python < 2.7, 1.2.4 will support Python 2.4 though 2.7. +1.3.0 will support Python 3 and Python 2.7 and 2.6. + +MySQLdb-2.0 is instead going to become moist-1.0. See https://github.com/farcepest/moist + +The Windows build has been simplified, and I plan to correct pre-built i386 packages built +against the python.org Python-2.7 package and MySQL Connector/C-6.0. Contact me if you +need ia64 packages. + +The connection's cursorclass (if not default) was being lost on reconnect. + +Newer versions of MySQL don't use OpenSSL and therefore don't have HAVE_SSL defined, but they do have +a different SSL library. Fixed this so SSL support would be enabled in this case. + +The regex that looked for SQL INSERT statement and VALUES in cursor.executemany() was made case-insensitive +again. + + +===================== + What's new in 1.2.3 +===================== + +ez_setup.py has been update to include various fixes that affect the build. + +Better Python version and dependency detection as well as eliminate exception +warnings under Python 2.6. + +Eliminated memory leaks related to Unicode and failed connections. + +Corrected connection .escape() functionality. + +Miscellaneous cleanups and and expanded testing suite to ensure ongoing release +quality. + +===================== + What's new in 1.2.2 +===================== + +The build system has been completely redone and should now build +on Windows without any patching; uses setuptools. + +Added compatibility for Python 2.5, including support for with statement. + +connection.ping() now takes an optional boolean argument which can +enable (or disable) automatic reconnection. + +Support returning SET columns as Python sets was removed due to an +API bug in MySQL; corresponding test removed. + +Added a test for single-character CHAR columns. + +BLOB columns are now returned as Python strings instead of byte arrays. + +BINARY character columns are always returned as Python strings, and not +unicode. + +Fixed a bug introduced in 1.2.1 where the new SHOW WARNINGS support broke +SSCursor. + +Only encode the query (convert to a string) when it is a unicode instance; +re-encoding encoded strings would break things. + +Make a deep copy of conv when connecting, since it can be modified. + +Added support for new VARCHAR and BIT column types. + +DBAPISet objects were broken, but nobody noticed. + + +======================== + What's new in 1.2.1_p2 +======================== + +There are some minor build fixes which probably only affect MySQL +older than 4.0. + +If you had MySQL older than 4.1, the new charset and sql_mode +parameters didn't work right. In fact, it was impossible to create +a connection due to the charset problem. + +If you are using MySQL-4.1 or newer, there is no practical difference +between 1.2.1 and 1.2.1_p2, and you don't need to upgrade. + + +===================== + What's new in 1.2.1 +===================== + +Switched to Subversion. Was going to do this for 1.3, but a +SourceForge CVS outage has forced the issue. + +Mapped a lot of new 4.1 and 5.0 error codes to Python exceptions + +Added an API call for mysql_set_character_set(charset) (MySQL > 5.0.7) + +Added an API call for mysql_get_character_set_info() (MySQL > 5.0.10) + +Revamped the build system. Edit site.cfg if necessary (probably not +in most cases) + +Python-2.3 is now the minimum version. + +Dropped support for mx.Datetime and stringtimes; always uses Python +datetime module now. + +Improved unit tests + +New connect() options: +* charset: sets character set, implies use_unicode +* sql_mode: sets SQL mode (i.e. ANSI, etc.; see MySQL docs) + +When using MySQL-4.1 or newer, enables MULTI_STATEMENTS + +When using MySQL-5.0 or newer, enables MULTI_RESULTS + +When using MySQL-4.1 or newer, more detailed warning messages +are produced + +SET columns returned as Python Set types; you can pass a Set as +a parameter to cursor.execute(). + +Support for the new MySQL-5.0 DECIMAL implementation + +Support for Python Decimal type + +Some use of weak references internally. Cursors no longer leak +if you don't close them. Connections still do, unfortunately. + +ursor.fetchXXXDict() methods raise DeprecationWarning + +cursor.begin() is making a brief reappearence. + +cursor.callproc() now works, with some limitations. + From 2cfa41948e7ede2b5745593cd848d93fc1f2b080 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 25 Nov 2015 12:09:06 +0000 Subject: [PATCH 011/396] cache pip correctly travis auto pip cache is disabled if the install command is changed --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e58e651e..7ef385e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,9 @@ sudo: false language: python python: "3.5" -cache: pip +cache: + directories: + - $HOME/.cache/pip install: - pip install tox From 4f53c17f3f943218ddc32811628976db98ab46ef Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 25 Nov 2015 12:11:24 +0000 Subject: [PATCH 012/396] Add documentation link --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 801d47ab..962fe4a5 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,8 @@ in your `requirements.txt` to avoid trying install source newest pacakge. 1. Download source by `git clone` or [zipfile](https://github.com/PyMySQL/mysqlclient-python/archive/master.zip). 2. Customize `site.cfg` 3. `python setup.py install` + +### Documentation + +Documentation is hosted on [Read The Docs](https://mysqlclient.readthedocs.org/) + From 32b1b97d88a19c49ede5167e8cb23d5e0891179b Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 25 Nov 2015 12:08:05 +0000 Subject: [PATCH 013/396] parallelise tox --- .travis.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ef385e2..b88cb347 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,22 @@ cache: install: - pip install tox +env: + matrix: + - TOX_ENV=py26 + - TOX_ENV=py27 + - TOX_ENV=pypy + - TOX_ENV=py33 + - TOX_ENV=py34 + - TOX_ENV=py35 + global: + - TESTDB=travis.cnf before_script: - "mysql --help" - "mysql --print-defaults" - "mysql -e 'create database mysqldb_test charset utf8mb4;'" -script: TESTDB=travis.cnf tox +script: tox -e $TOX_ENV # vim: sw=4 ts=4 sts=4 From 22455e5a32209723699cfe3edfca7c8f1b722130 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 13 Dec 2015 23:20:10 +0900 Subject: [PATCH 014/396] update doc --- doc/MySQLdb.rst | 100 ++++++++++++++++++++++++------------------------ doc/conf.py | 2 +- doc/index.rst | 5 --- 3 files changed, 51 insertions(+), 56 deletions(-) diff --git a/doc/MySQLdb.rst b/doc/MySQLdb.rst index 26138ec1..ac690900 100644 --- a/doc/MySQLdb.rst +++ b/doc/MySQLdb.rst @@ -1,50 +1,50 @@ -MySQLdb Package -=============== - -:mod:`MySQLdb` Package ----------------------- - -.. automodule:: MySQLdb.__init__ - :members: - :undoc-members: - :show-inheritance: - -:mod:`connections` Module -------------------------- - -.. automodule:: MySQLdb.connections - :members: Connection - :undoc-members: - :show-inheritance: - -:mod:`converters` Module ------------------------- - -.. automodule:: MySQLdb.converters - :members: - :undoc-members: - :show-inheritance: - -:mod:`cursors` Module ---------------------- - -.. automodule:: MySQLdb.cursors - :members: Cursor - :undoc-members: - :show-inheritance: - -:mod:`times` Module -------------------- - -.. automodule:: MySQLdb.times - :members: - :undoc-members: - :show-inheritance: - -Subpackages ------------ - -.. toctree:: - - MySQLdb.constants - +MySQLdb Package +=============== + +:mod:`MySQLdb` Package +---------------------- + +.. automodule:: MySQLdb + :members: + :undoc-members: + :show-inheritance: + +:mod:`connections` Module +------------------------- + +.. automodule:: MySQLdb.connections + :members: Connection + :undoc-members: + :show-inheritance: + +:mod:`converters` Module +------------------------ + +.. automodule:: MySQLdb.converters + :members: + :undoc-members: + :show-inheritance: + +:mod:`cursors` Module +--------------------- + +.. automodule:: MySQLdb.cursors + :members: Cursor + :undoc-members: + :show-inheritance: + +:mod:`times` Module +------------------- + +.. automodule:: MySQLdb.times + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + MySQLdb.constants + diff --git a/doc/conf.py b/doc/conf.py index ab36b07f..b9f58bd7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -16,7 +16,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- diff --git a/doc/index.rst b/doc/index.rst index a0b3fd57..2f3fdafa 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,8 +1,3 @@ -.. MySQLdb documentation master file, created by - sphinx-quickstart on Sun Oct 07 19:36:17 2012. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - Welcome to MySQLdb's documentation! =================================== From ae99670a08878c0e2a17c45a7f0c2884203c6fad Mon Sep 17 00:00:00 2001 From: aellis Date: Wed, 6 Jan 2016 14:55:20 -0500 Subject: [PATCH 015/396] fixing byte strings for python 3 cleanup validate python 3 version for decode --- MySQLdb/cursors.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index e2d74af1..cbb31b7a 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -45,7 +45,7 @@ class BaseCursor(object): """A base for Cursor classes. Useful attributes: - + description A tuple of DB API 7-tuples describing the columns in the last executed query; see PEP-249 for details. @@ -55,7 +55,7 @@ class BaseCursor(object): in the result set. Values correspond to those in MySQLdb.constants.FLAG. See MySQL documentation (C API) for more information. Non-standard extension. - + arraysize default number of rows fetchmany() will fetch """ @@ -63,12 +63,12 @@ class BaseCursor(object): from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \ DatabaseError, DataError, OperationalError, IntegrityError, \ InternalError, ProgrammingError, NotSupportedError - + _defer_warnings = False - + def __init__(self, connection): from weakref import ref - + self.connection = ref(connection) self.description = None self.description_flags = None @@ -82,7 +82,7 @@ def __init__(self, connection): self._warnings = 0 self._info = None self.rownumber = None - + def close(self): """Close the cursor. No further queries will be possible.""" try: @@ -124,7 +124,10 @@ def _warning_check(self): for w in warnings: self.messages.append((self.Warning, w)) for w in warnings: - warn(w[-1], self.Warning, 3) + msg = w[-1] + if not PY2 and isinstance(msg, bytes): + msg = msg.decode() + warn(msg, self.Warning, 3) elif self._info: self.messages.append((self.Warning, self._info)) warn(self._info, self.Warning, 3) @@ -159,10 +162,10 @@ def _do_get_result(self): self.lastrowid = db.insert_id() self._warnings = db.warning_count() self._info = db.info() - + def setinputsizes(self, *args): """Does nothing, required by DB API.""" - + def setoutputsizes(self, *args): """Does nothing, required by DB API.""" @@ -173,10 +176,10 @@ def _get_db(self): if con is None: raise ProgrammingError("cursor closed") return con - + def execute(self, query, args=None): """Execute a query. - + query -- string, query to execute on server args -- optional sequence or mapping, parameters to use with query. From 6ff138a597fbc0314536539e07731e9637987d73 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 12 Jan 2016 01:08:26 +0900 Subject: [PATCH 016/396] faster surrogateescape --- MySQLdb/connections.py | 23 +++++++++++++---------- MySQLdb/cursors.py | 27 +++++++++++++-------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 7ac040b5..f51106c3 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -1,10 +1,8 @@ """ - This module implements connections for MySQLdb. Presently there is only one class: Connection. Others are unlikely. However, you might want to make your own subclasses. In most cases, you will probably override Connection.default_cursor with a non-standard Cursor class. - """ from MySQLdb import cursors from MySQLdb.compat import unicode, PY2 @@ -15,6 +13,14 @@ import re +if not PY2: + # See http://bugs.python.org/issue24870 + _surrogateescape_table = [chr(i) if i < 0x80 else chr(i + 0xdc00) for i in range(256)] + + def _fast_surroundescape(s): + return s.decode('latin1').translate(_surrogateescape_table) + + def defaulterrorhandler(connection, cursor, errorclass, errorvalue): """ If cursor is not None, (errorclass, errorvalue) is appended to @@ -34,7 +40,7 @@ def defaulterrorhandler(connection, cursor, errorclass, errorvalue): del connection if isinstance(errorvalue, BaseException): raise errorvalue - if errorclass is not None: + if errorclass is not None: raise errorclass(errorvalue) else: raise Exception(errorvalue) @@ -291,24 +297,21 @@ def __exit__(self, exc, value, tb): self.commit() def literal(self, o): - """ - - If o is a single object, returns an SQL literal as a string. + """If o is a single object, returns an SQL literal as a string. If o is a non-string sequence, the items of the sequence are converted and returned as a sequence. Non-standard. For internal use; do not use this in your applications. - """ s = self.escape(o, self.encoders) - # Python 3 doesn't support % operation for bytes object. + # Python 3(~3.4) doesn't support % operation for bytes object. # We should decode it before using %. # Decoding with ascii and surrogateescape allows convert arbitrary # bytes to unicode and back again. # See http://python.org/dev/peps/pep-0383/ - if not PY2 and isinstance(s, bytes): - return s.decode('ascii', 'surrogateescape') + if not PY2 and isinstance(s, (bytes, bytearray)): + return _fast_surroundescape(s) return s def begin(self): diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index cbb31b7a..661ce354 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -187,7 +187,7 @@ def execute(self, query, args=None): parameter placeholder in the query. If a mapping is used, %(key)s must be used as the placeholder. - Returns long integer rows affected, if any + Returns integer represents rows affected, if any """ while self.nextset(): pass @@ -208,9 +208,12 @@ def execute(self, query, args=None): args = dict((key, db.literal(item)) for key, item in args.items()) else: args = tuple(map(db.literal, args)) - if not PY2 and isinstance(query, bytes): + if not PY2 and isinstance(query, (bytes, bytearray)): query = query.decode(db.unicode_literal.charset) - query = query % args + try: + query = query % args + except TypeError as m: + self.errorhandler(self, ProgrammingError, str(m)) if isinstance(query, unicode): query = query.encode(db.unicode_literal.charset, 'surrogateescape') @@ -218,17 +221,12 @@ def execute(self, query, args=None): res = None try: res = self._query(query) - except TypeError as m: - if m.args[0] in ("not enough arguments for format string", - "not all arguments converted"): - self.errorhandler(self, ProgrammingError, m.args[0]) - else: - self.errorhandler(self, TypeError, m) except Exception: exc, value = sys.exc_info()[:2] self.errorhandler(self, exc, value) self._executed = query - if not self._defer_warnings: self._warning_check() + if not self._defer_warnings: + self._warning_check() return res def executemany(self, query, args): @@ -369,13 +367,13 @@ def __iter__(self): class CursorStoreResultMixIn(object): - """This is a MixIn class which causes the entire result set to be stored on the client side, i.e. it uses mysql_store_result(). If the result set can be very large, consider adding a LIMIT clause to your query, or using CursorUseResultMixIn instead.""" - def _get_result(self): return self._get_db().store_result() + def _get_result(self): + return self._get_db().store_result() def _query(self, q): rowcount = self._do_query(q) @@ -390,9 +388,10 @@ def fetchone(self): """Fetches a single row from the cursor. None indicates that no more rows are available.""" self._check_executed() - if self.rownumber >= len(self._rows): return None + if self.rownumber >= len(self._rows): + return None result = self._rows[self.rownumber] - self.rownumber = self.rownumber+1 + self.rownumber = self.rownumber + 1 return result def fetchmany(self, size=None): From b6601fef9efff4786671bb2ecb99ee40d716d1aa Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 12 Jan 2016 15:12:46 +0900 Subject: [PATCH 017/396] Remove duplicated license file --- GPL-2.0 | 339 -------------------------------------------------------- 1 file changed, 339 deletions(-) delete mode 100644 GPL-2.0 diff --git a/GPL-2.0 b/GPL-2.0 deleted file mode 100644 index d159169d..00000000 --- a/GPL-2.0 +++ /dev/null @@ -1,339 +0,0 @@ - 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. From e76b691d9ea69146ac7f8127a6bdccae53b70973 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 25 Feb 2016 19:09:53 +0900 Subject: [PATCH 018/396] Remove mention to dead link --- doc/user_guide.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 232870a9..07016710 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -402,10 +402,6 @@ threadsafety a connection simultaneously, the MySQL client library will probably upchuck and die. You have been warned. - For threaded applications, try using a connection pool. - This can be done using the `Pool module`_. - - .. _`Pool module`: http://dustman.net/andy/python/Pool charset The character set used by the connection. In MySQL-4.1 and newer, From 49e401b3bc2c123b36949baeb409b91dcbdf85a6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 11 May 2016 11:25:53 +0900 Subject: [PATCH 019/396] Port executemany() implementation from PyMySQL --- MySQLdb/connections.py | 4 + MySQLdb/cursors.py | 183 ++++++++++++++++++++++++----------------- tests/test_cursor.py | 74 +++++++++++++++++ 3 files changed, 184 insertions(+), 77 deletions(-) create mode 100644 tests/test_cursor.py diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index f51106c3..d97e9185 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -278,6 +278,9 @@ def cursor(self, cursorclass=None): return (cursorclass or self.cursorclass)(self) def query(self, query): + # Since _mysql releases GIL while querying, we need immutable buffer. + if isinstance(query, bytearray): + query = bytes(query) if self.waiter is not None: self.send_query(query) self.waiter(self.fileno()) @@ -353,6 +356,7 @@ def set_character_set(self, charset): self.store_result() self.string_decoder.charset = py_charset self.unicode_literal.charset = py_charset + self.encoding = py_charset def set_sql_mode(self, sql_mode): """Set the connection sql_mode. See MySQL documentation for diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 661ce354..6260a02b 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -2,45 +2,34 @@ This module implements Cursors of various types for MySQLdb. By default, MySQLdb uses the Cursor class. - """ - +from __future__ import print_function, absolute_import +from functools import partial import re import sys -PY2 = sys.version_info[0] == 2 from MySQLdb.compat import unicode +from _mysql_exceptions import ( + Warning, Error, InterfaceError, DataError, + DatabaseError, OperationalError, IntegrityError, InternalError, + NotSupportedError, ProgrammingError) -restr = r""" - \s - values - \s* - ( - \( - [^()']* - (?: - (?: - (?:\( - # ( - editor highlighting helper - .* - \)) - | - ' - [^\\']* - (?:\\.[^\\']*)* - ' - ) - [^()']* - )* - \) - ) -""" -insert_values = re.compile(restr, re.S | re.I | re.X) +PY2 = sys.version_info[0] == 2 +if PY2: + text_type = unicode +else: + text_type = str + -from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \ - DatabaseError, OperationalError, IntegrityError, InternalError, \ - NotSupportedError, ProgrammingError +#: Regular expression for :meth:`Cursor.executemany`. +#: executemany only suports simple bulk insert. +#: You can use it to load large dataset. +RE_INSERT_VALUES = re.compile( + r"\s*((?:INSERT|REPLACE)\s.+\sVALUES?\s+)" + + r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" + + r"(\s*(?:ON DUPLICATE.*)?)\Z", + re.IGNORECASE | re.DOTALL) class BaseCursor(object): @@ -60,6 +49,12 @@ class BaseCursor(object): default number of rows fetchmany() will fetch """ + #: Max stetement size which :meth:`executemany` generates. + #: + #: Max size of allowed statement is max_allowed_packet - packet_header_size. + #: Default value of max_allowed_packet is 1048576. + max_stmt_length = 64*1024 + from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \ DatabaseError, DataError, OperationalError, IntegrityError, \ InternalError, ProgrammingError, NotSupportedError @@ -102,6 +97,32 @@ def __exit__(self, *exc_info): del exc_info self.close() + def _ensure_bytes(self, x, encoding=None): + if isinstance(x, text_type): + x = x.encode(encoding) + elif isinstance(x, (tuple, list)): + x = type(x)(self._ensure_bytes(v, encoding=encoding) for v in x) + return x + + def _escape_args(self, args, conn): + ensure_bytes = partial(self._ensure_bytes, encoding=conn.encoding) + + if isinstance(args, (tuple, list)): + if PY2: + args = tuple(map(ensure_bytes, args)) + return tuple(conn.escape(arg) for arg in args) + elif isinstance(args, dict): + if PY2: + args = dict((ensure_bytes(key), ensure_bytes(val)) for + (key, val) in args.items()) + return dict((key, conn.escape(val)) for (key, val) in args.items()) + else: + # If it's not a dictionary let's try escaping it anyways. + # Worst case it will throw a Value error + if PY2: + args = ensure_bytes(args) + return conn.escape(args) + def _check_executed(self): if not self._executed: self.errorhandler(self, ProgrammingError, "execute() first") @@ -230,62 +251,70 @@ def execute(self, query, args=None): return res def executemany(self, query, args): + # type: (str, list) -> int """Execute a multi-row query. - query -- string, query to execute on server - - args - - Sequence of sequences or mappings, parameters to use with - query. - - Returns long integer rows affected, if any. + :param query: query to execute on server + :param args: Sequence of sequences or mappings. It is used as parameter. + :return: Number of rows affected, if any. This method improves performance on multiple-row INSERT and REPLACE. Otherwise it is equivalent to looping over args with execute(). """ del self.messages[:] - db = self._get_db() - if not args: return - if PY2 and isinstance(query, unicode): - query = query.encode(db.unicode_literal.charset) - elif not PY2 and isinstance(query, bytes): - query = query.decode(db.unicode_literal.charset) - m = insert_values.search(query) - if not m: - r = 0 - for a in args: - r = r + self.execute(query, a) - return r - p = m.start(1) - e = m.end(1) - qv = m.group(1) - try: - q = [] - for a in args: - if isinstance(a, dict): - q.append(qv % dict((key, db.literal(item)) - for key, item in a.items())) + + if not args: + return + + m = RE_INSERT_VALUES.match(query) + if m: + q_prefix = m.group(1) % () + q_values = m.group(2).rstrip() + q_postfix = m.group(3) or '' + assert q_values[0] == '(' and q_values[-1] == ')' + return self._do_execute_many(q_prefix, q_values, q_postfix, args, + self.max_stmt_length, + self._get_db().encoding) + + self.rowcount = sum(self.execute(query, arg) for arg in args) + return self.rowcount + + def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding): + conn = self._get_db() + escape = self._escape_args + if isinstance(prefix, text_type): + prefix = prefix.encode(encoding) + if PY2 and isinstance(values, text_type): + values = values.encode(encoding) + if isinstance(postfix, text_type): + postfix = postfix.encode(encoding) + sql = bytearray(prefix) + args = iter(args) + v = values % escape(next(args), conn) + if isinstance(v, text_type): + if PY2: + v = v.encode(encoding) + else: + v = v.encode(encoding, 'surrogateescape') + sql += v + rows = 0 + for arg in args: + v = values % escape(arg, conn) + if isinstance(v, text_type): + if PY2: + v = v.encode(encoding) else: - q.append(qv % tuple([db.literal(item) for item in a])) - except TypeError as msg: - if msg.args[0] in ("not enough arguments for format string", - "not all arguments converted"): - self.errorhandler(self, ProgrammingError, msg.args[0]) + v = v.encode(encoding, 'surrogateescape') + if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length: + rows += self.execute(sql + postfix) + sql = bytearray(prefix) else: - self.errorhandler(self, TypeError, msg) - except (SystemExit, KeyboardInterrupt): - raise - except: - exc, value = sys.exc_info()[:2] - self.errorhandler(self, exc, value) - qs = '\n'.join([query[:p], ',\n'.join(q), query[e:]]) - if not PY2: - qs = qs.encode(db.unicode_literal.charset, 'surrogateescape') - r = self._query(qs) - if not self._defer_warnings: self._warning_check() - return r + sql += b',' + sql += v + rows += self.execute(sql + postfix) + self.rowcount = rows + return rows def callproc(self, procname, args=()): """Execute stored procedure procname with args diff --git a/tests/test_cursor.py b/tests/test_cursor.py new file mode 100644 index 00000000..04bb1c40 --- /dev/null +++ b/tests/test_cursor.py @@ -0,0 +1,74 @@ +import py.test +import MySQLdb.cursors +from configdb import connection_factory + + +_conns = [] +_tables = [] + +def connect(**kwargs): + conn = connection_factory(**kwargs) + _conns.append(conn) + return conn + + +def teardown_function(function): + if _tables: + c = _conns[0] + cur = c.cursor() + for t in _tables: + cur.execute("DROP TABLE %s" % (t,)) + cur.close() + del _tables[:] + + for c in _conns: + c.close() + del _conns[:] + + +def test_executemany(): + conn = connect() + cursor = conn.cursor() + + cursor.execute("create table test (data varchar(10))") + _tables.append("test") + + m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%s, %s)") + assert m is not None, 'error parse %s' + assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' + + m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id)s, %(name)s)") + assert m is not None, 'error parse %(name)s' + assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' + + m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s)") + assert m is not None, 'error parse %(id_name)s' + assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' + + m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s) ON duplicate update") + assert m is not None, 'error parse %(id_name)s' + assert m.group(3) == ' ON duplicate update', 'group 3 not ON duplicate update, bug in RE_INSERT_VALUES?' + + # cursor._executed myst bee "insert into test (data) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)" + # list args + data = range(10) + cursor.executemany("insert into test (data) values (%s)", data) + assert cursor._executed.endswith(",(7),(8),(9)"), 'execute many with %s not in one query' + + # dict args + data_dict = [{'data': i} for i in range(10)] + cursor.executemany("insert into test (data) values (%(data)s)", data_dict) + assert cursor._executed.endswith(",(7),(8),(9)"), 'execute many with %(data)s not in one query' + + # %% in column set + cursor.execute("""\ + CREATE TABLE percent_test ( + `A%` INTEGER, + `B%` INTEGER)""") + try: + q = "INSERT INTO percent_test (`A%%`, `B%%`) VALUES (%s, %s)" + assert MySQLdb.cursors.RE_INSERT_VALUES.match(q) is not None + cursor.executemany(q, [(3, 4), (5, 6)]) + assert cursor._executed.endswith("(3, 4),(5, 6)"), "executemany with %% not in one query" + finally: + cursor.execute("DROP TABLE IF EXISTS percent_test") From 57dd34dc10e3f8ec7d860dc8bc8e6baccb571b60 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 11 May 2016 15:24:36 +0900 Subject: [PATCH 020/396] fixup --- MySQLdb/connections.py | 1 - MySQLdb/cursors.py | 6 +++--- tests/test_cursor.py | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index d97e9185..1f69f5c2 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -65,7 +65,6 @@ def numeric_part(s): class Connection(_mysql.connection): - """MySQL Database Connection Object""" default_cursor = cursors.Cursor diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 6260a02b..1e0a3f94 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -110,18 +110,18 @@ def _escape_args(self, args, conn): if isinstance(args, (tuple, list)): if PY2: args = tuple(map(ensure_bytes, args)) - return tuple(conn.escape(arg) for arg in args) + return tuple(conn.literal(arg) for arg in args) elif isinstance(args, dict): if PY2: args = dict((ensure_bytes(key), ensure_bytes(val)) for (key, val) in args.items()) - return dict((key, conn.escape(val)) for (key, val) in args.items()) + return dict((key, conn.literal(val)) for (key, val) in args.items()) else: # If it's not a dictionary let's try escaping it anyways. # Worst case it will throw a Value error if PY2: args = ensure_bytes(args) - return conn.escape(args) + return conn.literal(args) def _check_executed(self): if not self._executed: diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 04bb1c40..bfdcb33a 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -53,12 +53,12 @@ def test_executemany(): # list args data = range(10) cursor.executemany("insert into test (data) values (%s)", data) - assert cursor._executed.endswith(",(7),(8),(9)"), 'execute many with %s not in one query' + assert cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %s not in one query' # dict args data_dict = [{'data': i} for i in range(10)] cursor.executemany("insert into test (data) values (%(data)s)", data_dict) - assert cursor._executed.endswith(",(7),(8),(9)"), 'execute many with %(data)s not in one query' + assert cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %(data)s not in one query' # %% in column set cursor.execute("""\ @@ -69,6 +69,6 @@ def test_executemany(): q = "INSERT INTO percent_test (`A%%`, `B%%`) VALUES (%s, %s)" assert MySQLdb.cursors.RE_INSERT_VALUES.match(q) is not None cursor.executemany(q, [(3, 4), (5, 6)]) - assert cursor._executed.endswith("(3, 4),(5, 6)"), "executemany with %% not in one query" + assert cursor._executed.endswith(b"(3, 4),(5, 6)"), "executemany with %% not in one query" finally: cursor.execute("DROP TABLE IF EXISTS percent_test") From a4b8f8c9b844bd6e363bc642ab221a8498c4528c Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 May 2016 10:54:58 +0100 Subject: [PATCH 021/396] Revert "cache pip correctly" --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b88cb347..6f479e52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,7 @@ sudo: false language: python python: "3.5" -cache: - directories: - - $HOME/.cache/pip +cache: pip install: - pip install tox From 012e91a03a7662b865ea705b838aef75e3018b09 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 12 May 2016 00:29:12 +0900 Subject: [PATCH 022/396] Fix accessing buffer of decrefed string fixes #78 --- _mysql.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/_mysql.c b/_mysql.c index bcd15b84..7e88429e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -530,10 +530,12 @@ _mysql_ConnectionObject_Initialize( PyObject *ssl = NULL; #if HAVE_OPENSSL char *key = NULL, *cert = NULL, *ca = NULL, - *capath = NULL, *cipher = NULL; + *capath = NULL, *cipher = NULL; + PyObject *ssl_keepref[5] = {}; + int n_ssl_keepref = 0; #endif char *host = NULL, *user = NULL, *passwd = NULL, - *db = NULL, *unix_socket = NULL; + *db = NULL, *unix_socket = NULL; unsigned int port = 0; unsigned int client_flag = 0; static char *kwlist[] = { "host", "user", "passwd", "db", "port", @@ -586,11 +588,11 @@ _mysql_ConnectionObject_Initialize( #ifdef IS_PY3K #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ - if(t){d=PyUnicode_AsUTF8(t);Py_DECREF(t);}\ + if(t){d=PyUnicode_AsUTF8(t);ssl_keepref[n_ssl_keepref++]=t;}\ PyErr_Clear();} #else #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ - if(t){d=PyString_AsString(t);Py_DECREF(t);}\ + if(t){d=PyString_AsString(t);ssl_keepref[n_ssl_keepref++]=t;}\ PyErr_Clear();} #endif @@ -645,9 +647,9 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); #if HAVE_OPENSSL - if (ssl) - mysql_ssl_set(&(self->connection), - key, cert, ca, capath, cipher); + if (ssl) { + mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); + } #endif conn = mysql_real_connect(&(self->connection), host, user, passwd, db, @@ -655,12 +657,22 @@ _mysql_ConnectionObject_Initialize( Py_END_ALLOW_THREADS ; +#if HAVE_OPENSSL + if (ssl) { + int i; + for (i=0; i Date: Mon, 16 May 2016 08:12:21 +0300 Subject: [PATCH 023/396] Use assertEqual instead of deprecated assertEquals --- tests/capabilities.py | 8 ++++---- tests/test_MySQLdb_capabilities.py | 4 ++-- tests/test_MySQLdb_dbapi20.py | 4 ++-- tests/test_MySQLdb_nonstandard.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/capabilities.py b/tests/capabilities.py index cbbe24fe..31aa398e 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -91,11 +91,11 @@ def check_data_integrity(self, columndefs, generator): # verify self.cursor.execute('select * from %s' % self.table) l = self.cursor.fetchall() - self.assertEquals(len(l), self.rows) + self.assertEqual(len(l), self.rows) try: for i in range(self.rows): for j in range(len(columndefs)): - self.assertEquals(l[i][j], generator(i,j)) + self.assertEqual(l[i][j], generator(i,j)) finally: if not self.debug: self.cursor.execute('drop table %s' % (self.table)) @@ -116,10 +116,10 @@ def generator(row, col): self.connection.commit() self.cursor.execute('select * from %s' % self.table) l = self.cursor.fetchall() - self.assertEquals(len(l), self.rows) + self.assertEqual(len(l), self.rows) for i in range(self.rows): for j in range(len(columndefs)): - self.assertEquals(l[i][j], generator(i,j)) + self.assertEqual(l[i][j], generator(i,j)) delete_statement = 'delete from %s where col1=%%s' % self.table self.cursor.execute(delete_statement, (0,)) self.cursor.execute('select col1 from %s where col1=%s' % \ diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 23d7d9ef..1ec32f71 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -56,8 +56,8 @@ def test_stored_procedures(self): c.callproc('test_sp', ('larch',)) rows = c.fetchall() - self.assertEquals(len(rows), 1) - self.assertEquals(rows[0][0], 3) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0][0], 3) c.nextset() c.execute("DROP PROCEDURE test_sp") diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index d8598dd3..3fef3f5f 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -189,8 +189,8 @@ def test_nextset(self): s=cur.nextset() if s: empty = cur.fetchall() - self.assertEquals(len(empty), 0, - "non-empty result set after other result sets") + self.assertEqual(len(empty), 0, + "non-empty result set after other result sets") #warn("Incompatibility: MySQL returns an empty result set for the CALL itself", # Warning) #assert s == None,'No more return sets, should return None' diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index 2ca5a544..7efe265c 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -67,8 +67,8 @@ def test_thread_id(self): "thread_id shouldn't accept arguments.") def test_affected_rows(self): - self.assertEquals(self.conn.affected_rows(), 0, - "Should return 0 before we do anything.") + self.assertEqual(self.conn.affected_rows(), 0, + "Should return 0 before we do anything.") #def test_debug(self): From 9e3ca789dcfff12654cee057b50dd49ce6c5bb0c Mon Sep 17 00:00:00 2001 From: Marco Montagna Date: Mon, 23 May 2016 15:54:35 -0700 Subject: [PATCH 024/396] Fix readme spelling and grammar. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 962fe4a5..80a33657 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ On Windows, there are binary wheel you can install without MySQLConnector/C or M `pip install mysqlclient` NOTE: Wheels for Windows may be not released with source package. You should pin version -in your `requirements.txt` to avoid trying install source newest pacakge. +in your `requirements.txt` to avoid trying to install newest source package. ### Install from source From 3998e7b0ff2f27f64fcfa969865affa86340ddb6 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Sun, 29 May 2016 14:47:42 +0100 Subject: [PATCH 025/396] Convert readthedocs link for their .org -> .io migration for hosted projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per their email ‘Changes to project subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80a33657..429770f8 100644 --- a/README.md +++ b/README.md @@ -42,5 +42,5 @@ in your `requirements.txt` to avoid trying to install newest source package. ### Documentation -Documentation is hosted on [Read The Docs](https://mysqlclient.readthedocs.org/) +Documentation is hosted on [Read The Docs](https://mysqlclient.readthedocs.io/) From ee6ad1ccf4b19888df538faff8923511e33e4193 Mon Sep 17 00:00:00 2001 From: jay park Date: Thu, 9 Jun 2016 02:43:00 +0900 Subject: [PATCH 026/396] unicode string in argument of string raises error Hi, I've got 'surrogates not allowed' error when I put unicode string into stored procedure's argument. This is pr for the issue. cf: https://github.com/PyMySQL/mysqlclient-python/issues/90 . --- MySQLdb/cursors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 1e0a3f94..75b9aeee 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -358,7 +358,7 @@ def callproc(self, procname, args=()): ','.join(['@_%s_%d' % (procname, i) for i in range(len(args))])) if isinstance(q, unicode): - q = q.encode(db.unicode_literal.charset) + q = q.encode(db.unicode_literal.charset, 'surrogateescape') self._query(q) self._executed = q if not self._defer_warnings: From 06f2b665989b22c10184e83dc49ddf3ac79527b5 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 14 Jun 2016 18:01:46 +0900 Subject: [PATCH 027/396] Cursor.connection is real reference --- MySQLdb/cursors.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 1e0a3f94..a7def7f9 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -60,11 +60,10 @@ class BaseCursor(object): InternalError, ProgrammingError, NotSupportedError _defer_warnings = False + connection = None def __init__(self, connection): - from weakref import ref - - self.connection = ref(connection) + self.connection = connection self.description = None self.description_flags = None self.rowcount = -1 @@ -81,7 +80,7 @@ def __init__(self, connection): def close(self): """Close the cursor. No further queries will be possible.""" try: - if self.connection is None or self.connection() is None: + if self.connection is None: return while self.nextset(): pass @@ -192,8 +191,6 @@ def setoutputsizes(self, *args): def _get_db(self): con = self.connection - if con is not None: - con = con() if con is None: raise ProgrammingError("cursor closed") return con From 66029d64060fca03f3d0b22661b1b4cf9849ef03 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 28 Jun 2016 17:36:57 +0900 Subject: [PATCH 028/396] Support 'database' and 'password' kwargs --- MySQLdb/connections.py | 148 ++++++++++++++++++----------------------- _mysql.c | 10 +-- 2 files changed, 67 insertions(+), 91 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 1f69f5c2..d8406db8 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -76,89 +76,66 @@ def __init__(self, *args, **kwargs): that you only use keyword parameters. Consult the MySQL C API documentation for more information. - host - string, host to connect - - user - string, user to connect as - - passwd - string, password to use - - db - string, database to use - - port - integer, TCP/IP port to connect to - - unix_socket - string, location of unix_socket to use - - conv - conversion dictionary, see MySQLdb.converters - - connect_timeout - number of seconds to wait before the connection attempt - fails. - - compress - if set, compression is enabled - - named_pipe - if set, a named pipe is used to connect (Windows only) - - init_command - command which is run once the connection is created - - read_default_file - file from which default client values are read - - read_default_group - configuration group to use from the default file - - cursorclass - class object, used to create cursors (keyword only) - - use_unicode - If True, text-like columns are returned as unicode objects - using the connection's character set. Otherwise, text-like - columns are returned as strings. columns are returned as - normal strings. Unicode objects will always be encoded to - the connection's character set regardless of this setting. - Default to False on Python 2 and True on Python 3. - - charset - If supplied, the connection character set will be changed - to this character set (MySQL-4.1 and newer). This implies - use_unicode=True. - - sql_mode - If supplied, the session SQL mode will be changed to this - setting (MySQL-4.1 and newer). For more details and legal - values, see the MySQL documentation. - - client_flag - integer, flags to use or 0 - (see MySQL docs or constants/CLIENTS.py) - - ssl - dictionary or mapping, contains SSL connection parameters; - see the MySQL documentation for more details - (mysql_ssl_set()). If this is set, and the client does not - support SSL, NotSupportedError will be raised. - - local_infile - integer, non-zero enables LOAD LOCAL INFILE; zero disables - - autocommit - If False (default), autocommit is disabled. - If True, autocommit is enabled. - If None, autocommit isn't set and server default is used. - - waiter - Callable accepts fd as an argument. It is called after sending - query and before reading response. - This is useful when using with greenlet and async io. + :param str host: host to connect + :param str user: user to connect as + :param str password: password to use + :param str passwd: alias of password, for backward compatibility + :param str database: database to use + :param str db: alias of database, for backward compatibility + :param int port: TCP/IP port to connect to + :param str unix_socket: location of unix_socket to use + :param dict conv: conversion dictionary, see MySQLdb.converters + :param int connect_timeout: + number of seconds to wait before the connection attempt fails. + + :param bool compress: if set, compression is enabled + :param str named_pipe: if set, a named pipe is used to connect (Windows only) + :param str init_command: + command which is run once the connection is created + + :param str read_default_file: + file from which default client values are read + + :param str read_default_group: + configuration group to use from the default file + + :param type cursorclass: + class object, used to create cursors (keyword only) + + :param str use_unicode: + If True, text-like columns are returned as unicode objects + using the connection's character set. Otherwise, text-like + columns are returned as strings. columns are returned as + normal strings. Unicode objects will always be encoded to + the connection's character set regardless of this setting. + Default to False on Python 2 and True on Python 3. + + :param str charset: + If supplied, the connection character set will be changed + to this character set (MySQL-4.1 and newer). This implies + use_unicode=True. + + :param str sql_mode: + If supplied, the session SQL mode will be changed to this + setting (MySQL-4.1 and newer). For more details and legal + values, see the MySQL documentation. + + :param int client_flag: + flags to use or 0 (see MySQL docs or constants/CLIENTS.py) + + :param dict ssl: + dictionary or mapping contains SSL connection parameters; + see the MySQL documentation for more details + (mysql_ssl_set()). If this is set, and the client does not + support SSL, NotSupportedError will be raised. + + :param bool local_infile: + enables LOAD LOCAL INFILE; zero disables + + :param bool autocommit: + If False (default), autocommit is disabled. + If True, autocommit is enabled. + If None, autocommit isn't set and server default is used. There are a number of undocumented, non-standard methods. See the documentation for the MySQL C API for some hints on what they do. @@ -169,6 +146,11 @@ class object, used to create cursors (keyword only) kwargs2 = kwargs.copy() + if 'database' in kwargs2: + kwargs2['db'] = kwargs2.pop('database') + if 'password' in kwargs2: + kwargs2['passwd'] = kwargs2.pop('password') + if 'conv' in kwargs: conv = kwargs['conv'] else: diff --git a/_mysql.c b/_mysql.c index 7e88429e..25af5ea9 100644 --- a/_mysql.c +++ b/_mysql.c @@ -95,11 +95,7 @@ typedef struct { extern PyTypeObject _mysql_ResultObject_Type; static int _mysql_server_init_done = 0; -#if MYSQL_VERSION_ID >= 40000 #define check_server_init(x) if (!_mysql_server_init_done) { if (mysql_server_init(0, NULL, NULL)) { _mysql_Exception(NULL); return x; } else { _mysql_server_init_done = 1;} } -#else -#define check_server_init(x) if (!_mysql_server_init_done) _mysql_server_init_done = 1 -#endif #if MYSQL_VERSION_ID >= 50500 #define HAVE_OPENSSL 1 @@ -2947,13 +2943,11 @@ _mysql_NewException( char *name) { PyObject *e; - if (!(e = PyDict_GetItemString(edict, name))) return NULL; - if (PyDict_SetItemString(dict, name, e)) return NULL; -#ifdef PYPY_VERSION + if (PyDict_SetItemString(dict, name, e)) + return NULL; Py_INCREF(e); -#endif return e; } From 0f6e5d7ddfd21878dfa11bfd9e9bfb5cc782b56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 6 Jul 2016 16:29:42 +0300 Subject: [PATCH 029/396] Doc and comment link fixes --- INSTALL | 2 +- _mysql_exceptions.py | 2 +- doc/FAQ.rst | 2 +- doc/user_guide.rst | 4 ++-- setup.cfg | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/INSTALL b/INSTALL index 9caf5bcc..616b5902 100644 --- a/INSTALL +++ b/INSTALL @@ -175,7 +175,7 @@ Packaged as `mysql-python`_. :: # emerge mysql-python # emerge zmysqlda # if you use Zope -.. _`mysql-python`: http://packages.gentoo.org/search/?sstring=mysql-python +.. _`mysql-python`: https://packages.gentoo.org/packages/search?q=mysql-python BSD diff --git a/_mysql_exceptions.py b/_mysql_exceptions.py index 3241e740..74b765a7 100644 --- a/_mysql_exceptions.py +++ b/_mysql_exceptions.py @@ -2,7 +2,7 @@ These classes are dictated by the DB API v2.0: - http://www.python.org/topics/database/DatabaseAPI-2.0.html + https://www.python.org/dev/peps/pep-0249/ """ try: diff --git a/doc/FAQ.rst b/doc/FAQ.rst index 79e828a0..ab06a212 100644 --- a/doc/FAQ.rst +++ b/doc/FAQ.rst @@ -71,7 +71,7 @@ Solutions: * reconfigure your system so that the MySQL libraries are on the default loader path. In Linux, you edit /etc/ld.so.conf and run ldconfig. For Solaris, see `Linker and Libraries Guide - `_. + `_. ImportError: ld.so.1: python: fatal: libmtmalloc.so.1: DF_1_NOOPEN tagged object may not be dlopen()'ed diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 07016710..b39a230a 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -152,7 +152,7 @@ This does what the previous example does, but gets the username and password and other parameters from ~/.my.cnf (UNIX-like systems). Read about `option files`_ for more details. -.. _`option files`: http://dev.mysql.com/doc/mysql/en/Option_files.html +.. _`option files`: http://dev.mysql.com/doc/refman/en/option-files.html So now you have an open connection as ``db`` and want to do a query. Well, there are no cursors in MySQL, and no parameter @@ -366,7 +366,7 @@ connect(parameters...) an exception is raised. *This must be a keyword parameter.* -.. _mysql_ssl_set: http://dev.mysql.com/doc/mysql/en/mysql_ssl_set.html +.. _mysql_ssl_set: http://dev.mysql.com/doc/refman/en/mysql-ssl-set.html apilevel diff --git a/setup.cfg b/setup.cfg index 5b21f6d2..fea65ab0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ test_suite = nose.collector [build_ext] ## Only uncomment/set these if the default configuration doesn't work -## Also see http://docs.python.org/dist/setup-config.html +## Also see https://docs.python.org/distutils/configfile.html # include-dirs = ? # library-dirs = ? # link-objects = ? From ac1fb2dddd36ae33b941b21508ecdbf52151628d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 25 Jul 2016 03:55:38 +0900 Subject: [PATCH 030/396] mysql_affected_rows may return -1 --- _mysql.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 7e88429e..db9f475a 100644 --- a/_mysql.c +++ b/_mysql.c @@ -827,9 +827,13 @@ _mysql_ConnectionObject_affected_rows( _mysql_ConnectionObject *self, PyObject *args) { + my_ulonglong ret; if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); - return PyLong_FromUnsignedLongLong(mysql_affected_rows(&(self->connection))); + ret = mysql_affected_rows(&(self->connection)); + if (ret == (my_ulonglong)-1) + return PyInt_FromLong(-1); + return PyLong_FromUnsignedLongLong(ret); } static char _mysql_debug__doc__[] = From fb842fbdd50c2adaa2f5488a5a40c34dfaa7fcf6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 27 Jul 2016 01:49:06 +0900 Subject: [PATCH 031/396] Use METH_NOARGS (#103) --- _mysql.c | 195 ++++++++++++++++++------------------------------------- 1 file changed, 63 insertions(+), 132 deletions(-) diff --git a/_mysql.c b/_mysql.c index ca77418c..cffde1ef 100644 --- a/_mysql.c +++ b/_mysql.c @@ -346,20 +346,18 @@ static PyObject *_mysql_server_end( return _mysql_Exception(NULL); } -#if MYSQL_VERSION_ID >= 32314 static char _mysql_thread_safe__doc__[] = "Indicates whether the client is compiled as thread-safe."; static PyObject *_mysql_thread_safe( PyObject *self, - PyObject *args) { + PyObject *noargs) +{ PyObject *flag; - if (!PyArg_ParseTuple(args, "")) return NULL; check_server_init(NULL); if (!(flag=PyInt_FromLong((long)mysql_thread_safe()))) return NULL; return flag; } -#endif static char _mysql_ResultObject__doc__[] = "result(connection, use=0, converter={}) -- Result set from a query.\n\ @@ -793,11 +791,8 @@ static char _mysql_ConnectionObject_close__doc__[] = static PyObject * _mysql_ConnectionObject_close( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (args) { - if (!PyArg_ParseTuple(args, "")) return NULL; - } if (self->open) { Py_BEGIN_ALLOW_THREADS mysql_close(&(self->connection)); @@ -821,10 +816,9 @@ Non-standard. Use Cursor.rowcount.\n\ static PyObject * _mysql_ConnectionObject_affected_rows( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { my_ulonglong ret; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); ret = mysql_affected_rows(&(self->connection)); if (ret == (my_ulonglong)-1) @@ -859,10 +853,9 @@ this to work. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_dump_debug_info( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { int err; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_dump_debug_info(&(self->connection)); @@ -883,15 +876,7 @@ _mysql_ConnectionObject_autocommit( int flag, err; if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 err = mysql_autocommit(&(self->connection), flag); -#else - { - char query[256]; - snprintf(query, 256, "SET AUTOCOMMIT=%d", flag); - err = mysql_query(&(self->connection), query); - } -#endif Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); Py_INCREF(Py_None); @@ -918,16 +903,11 @@ static char _mysql_ConnectionObject_commit__doc__[] = static PyObject * _mysql_ConnectionObject_commit( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { int err; - if (!PyArg_ParseTuple(args, "")) return NULL; Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 err = mysql_commit(&(self->connection)); -#else - err = mysql_query(&(self->connection), "COMMIT"); -#endif Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); Py_INCREF(Py_None); @@ -940,16 +920,11 @@ static char _mysql_ConnectionObject_rollback__doc__[] = static PyObject * _mysql_ConnectionObject_rollback( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { int err; - if (!PyArg_ParseTuple(args, "")) return NULL; Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 err = mysql_rollback(&(self->connection)); -#else - err = mysql_query(&(self->connection), "ROLLBACK"); -#endif Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); Py_INCREF(Py_None); @@ -972,22 +947,16 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_next_result( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { int err; - if (!PyArg_ParseTuple(args, "")) return NULL; Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 err = mysql_next_result(&(self->connection)); -#else - err = -1; -#endif Py_END_ALLOW_THREADS if (err > 0) return _mysql_Exception(self); return PyInt_FromLong(err); } -#if MYSQL_VERSION_ID >= 40100 static char _mysql_ConnectionObject_set_server_option__doc__[] = "set_server_option(option) -- Enables or disables an option\n\ @@ -1025,9 +994,8 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_sqlstate( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; return PyString_FromString(mysql_sqlstate(&(self->connection))); } @@ -1040,14 +1008,11 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_warning_count( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; return PyInt_FromLong(mysql_warning_count(&(self->connection))); } -#endif - static char _mysql_ConnectionObject_errno__doc__[] = "Returns the error code for the most recently invoked API function\n\ that can succeed or fail. A return value of zero means that no error\n\ @@ -1057,9 +1022,8 @@ occurred.\n\ static PyObject * _mysql_ConnectionObject_errno( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); return PyInt_FromLong((long)mysql_errno(&(self->connection))); } @@ -1073,9 +1037,8 @@ occurred.\n\ static PyObject * _mysql_ConnectionObject_error( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); return PyString_FromString(mysql_error(&(self->connection))); } @@ -1312,12 +1275,11 @@ the Cursor.description attribute.\n\ static PyObject * _mysql_ResultObject_describe( _mysql_ResultObject *self, - PyObject *args) + PyObject *noargs) { PyObject *d; MYSQL_FIELD *fields; unsigned int i, n; - if (!PyArg_ParseTuple(args, "")) return NULL; check_result_connection(self); n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); @@ -1348,12 +1310,11 @@ static char _mysql_ResultObject_field_flags__doc__[] = static PyObject * _mysql_ResultObject_field_flags( _mysql_ResultObject *self, - PyObject *args) + PyObject *noargs) { PyObject *d; MYSQL_FIELD *fields; unsigned int i, n; - if (!PyArg_ParseTuple(args, "")) return NULL; check_result_connection(self); n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); @@ -1682,16 +1643,11 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_character_set_name( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { const char *s; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); -#if MYSQL_VERSION_ID >= 32321 s = mysql_character_set_name(&(self->connection)); -#else - s = "latin1"; -#endif return PyString_FromString(s); } @@ -1744,12 +1700,11 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_character_set_info( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { PyObject *result; MY_CHARSET_INFO cs; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); mysql_get_character_set_info(&(self->connection), &cs); if (!(result = PyDict_New())) return NULL; @@ -1773,9 +1728,8 @@ the client library version."; static PyObject * _mysql_get_client_info( PyObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_server_init(NULL); return PyString_FromString(mysql_get_client_info()); } @@ -1788,9 +1742,8 @@ version. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_host_info( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); return PyString_FromString(mysql_get_host_info(&(self->connection))); } @@ -1803,9 +1756,8 @@ used by the current connection. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_proto_info( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); } @@ -1818,9 +1770,8 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_server_info( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); return PyString_FromString(mysql_get_server_info(&(self->connection))); } @@ -1834,10 +1785,9 @@ Cursor.messages.\n\ static PyObject * _mysql_ConnectionObject_info( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { const char *s; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); s = mysql_info(&(self->connection)); if (s) return PyString_FromString(s); @@ -1869,10 +1819,9 @@ in the server.\n\ static PyObject * _mysql_ConnectionObject_insert_id( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { my_ulonglong r; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_insert_id(&(self->connection)); @@ -1910,15 +1859,10 @@ on most cursor classes. Use Cursor.rowcount.\n\ static PyObject * _mysql_ConnectionObject_field_count( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); -#if MYSQL_VERSION_ID < 32224 - return PyInt_FromLong((long)mysql_num_fields(&(self->connection))); -#else return PyInt_FromLong((long)mysql_field_count(&(self->connection))); -#endif } static char _mysql_ResultObject_num_fields__doc__[] = @@ -1927,9 +1871,8 @@ static char _mysql_ResultObject_num_fields__doc__[] = static PyObject * _mysql_ResultObject_num_fields( _mysql_ResultObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_result_connection(self); return PyInt_FromLong((long)mysql_num_fields(self->result)); } @@ -1943,9 +1886,8 @@ set has been read.\n\ static PyObject * _mysql_ResultObject_num_rows( _mysql_ResultObject *self, - PyObject *args) + PyObject *noargs) { - if (!PyArg_ParseTuple(args, "")) return NULL; check_result_connection(self); return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); } @@ -2040,7 +1982,8 @@ static char _mysql_ConnectionObject_read_query_result__doc__[] = static PyObject * _mysql_ConnectionObject_read_query_result( - _mysql_ConnectionObject *self) + _mysql_ConnectionObject *self, + PyObject *noargs) { int r; MYSQL *mysql = &(self->connection); @@ -2091,10 +2034,9 @@ have shutdown privileges. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_shutdown( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { int r; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_shutdown(&(self->connection) @@ -2118,10 +2060,9 @@ questions, reloads, and open tables. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_stat( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { const char *s; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); Py_BEGIN_ALLOW_THREADS s = mysql_stat(&(self->connection)); @@ -2139,12 +2080,11 @@ None is returned. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_store_result( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; _mysql_ResultObject *r=NULL; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); arglist = Py_BuildValue("(OiO)", self, 0, self->converter); if (!arglist) goto error; @@ -2180,10 +2120,9 @@ Non-standard."; static PyObject * _mysql_ConnectionObject_thread_id( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { unsigned long pid; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); Py_BEGIN_ALLOW_THREADS pid = mysql_thread_id(&(self->connection)); @@ -2200,12 +2139,11 @@ None is returned. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_use_result( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *noargs) { PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; _mysql_ResultObject *r=NULL; - if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); arglist = Py_BuildValue("(OiO)", self, 1, self->converter); if (!arglist) return NULL; @@ -2296,10 +2234,9 @@ static char _mysql_ResultObject_row_tell__doc__[] = static PyObject * _mysql_ResultObject_row_tell( _mysql_ResultObject *self, - PyObject *args) + PyObject *noargs) { MYSQL_ROW_OFFSET r; - if (!PyArg_ParseTuple(args, "")) return NULL; check_result_connection(self); if (self->use) { PyErr_SetString(_mysql_ProgrammingError, @@ -2333,7 +2270,7 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "affected_rows", (PyCFunction)_mysql_ConnectionObject_affected_rows, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_affected_rows__doc__ }, { @@ -2351,22 +2288,21 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "commit", (PyCFunction)_mysql_ConnectionObject_commit, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_commit__doc__ }, { "rollback", (PyCFunction)_mysql_ConnectionObject_rollback, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_rollback__doc__ }, { "next_result", (PyCFunction)_mysql_ConnectionObject_next_result, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_next_result__doc__ }, -#if MYSQL_VERSION_ID >= 40100 { "set_server_option", (PyCFunction)_mysql_ConnectionObject_set_server_option, @@ -2376,28 +2312,25 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "sqlstate", (PyCFunction)_mysql_ConnectionObject_sqlstate, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_sqlstate__doc__ }, { "warning_count", (PyCFunction)_mysql_ConnectionObject_warning_count, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_warning_count__doc__ }, -#endif -#if MYSQL_VERSION_ID >= 32303 { "change_user", (PyCFunction)_mysql_ConnectionObject_change_user, METH_VARARGS | METH_KEYWORDS, _mysql_ConnectionObject_change_user__doc__ }, -#endif { "character_set_name", (PyCFunction)_mysql_ConnectionObject_character_set_name, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_character_set_name__doc__ }, #if MYSQL_VERSION_ID >= 50007 @@ -2412,14 +2345,14 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "get_character_set_info", (PyCFunction)_mysql_ConnectionObject_get_character_set_info, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_get_character_set_info__doc__ }, #endif { "close", (PyCFunction)_mysql_ConnectionObject_close, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_close__doc__ }, { @@ -2431,7 +2364,7 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "dump_debug_info", (PyCFunction)_mysql_ConnectionObject_dump_debug_info, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_dump_debug_info__doc__ }, { @@ -2449,49 +2382,49 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "error", (PyCFunction)_mysql_ConnectionObject_error, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_error__doc__ }, { "errno", (PyCFunction)_mysql_ConnectionObject_errno, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_errno__doc__ }, { "field_count", (PyCFunction)_mysql_ConnectionObject_field_count, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_field_count__doc__ }, { "get_host_info", (PyCFunction)_mysql_ConnectionObject_get_host_info, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_get_host_info__doc__ }, { "get_proto_info", (PyCFunction)_mysql_ConnectionObject_get_proto_info, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_get_proto_info__doc__ }, { "get_server_info", (PyCFunction)_mysql_ConnectionObject_get_server_info, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_get_server_info__doc__ }, { "info", (PyCFunction)_mysql_ConnectionObject_info, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_info__doc__ }, { "insert_id", (PyCFunction)_mysql_ConnectionObject_insert_id, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_insert_id__doc__ }, { @@ -2533,19 +2466,19 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "shutdown", (PyCFunction)_mysql_ConnectionObject_shutdown, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_shutdown__doc__ }, { "stat", (PyCFunction)_mysql_ConnectionObject_stat, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_stat__doc__ }, { "store_result", (PyCFunction)_mysql_ConnectionObject_store_result, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_store_result__doc__ }, { @@ -2556,13 +2489,13 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "thread_id", (PyCFunction)_mysql_ConnectionObject_thread_id, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_thread_id__doc__ }, { "use_result", (PyCFunction)_mysql_ConnectionObject_use_result, - METH_VARARGS, + METH_NOARGS, _mysql_ConnectionObject_use_result__doc__ }, {NULL, NULL} /* sentinel */ @@ -2623,13 +2556,13 @@ static PyMethodDef _mysql_ResultObject_methods[] = { { "row_tell", (PyCFunction)_mysql_ResultObject_row_tell, - METH_VARARGS, + METH_NOARGS, _mysql_ResultObject_row_tell__doc__ }, { "describe", (PyCFunction)_mysql_ResultObject_describe, - METH_VARARGS, + METH_NOARGS, _mysql_ResultObject_describe__doc__ }, { @@ -2641,19 +2574,19 @@ static PyMethodDef _mysql_ResultObject_methods[] = { { "field_flags", (PyCFunction)_mysql_ResultObject_field_flags, - METH_VARARGS, + METH_NOARGS, _mysql_ResultObject_field_flags__doc__ }, { "num_fields", (PyCFunction)_mysql_ResultObject_num_fields, - METH_VARARGS, + METH_NOARGS, _mysql_ResultObject_num_fields__doc__ }, { "num_rows", (PyCFunction)_mysql_ResultObject_num_rows, - METH_VARARGS, + METH_NOARGS, _mysql_ResultObject_num_rows__doc__ }, {NULL, NULL} /* sentinel */ @@ -2914,17 +2847,15 @@ _mysql_methods[] = { { "get_client_info", (PyCFunction)_mysql_get_client_info, - METH_VARARGS, + METH_NOARGS, _mysql_get_client_info__doc__ }, -#if MYSQL_VERSION_ID >= 32314 { "thread_safe", (PyCFunction)_mysql_thread_safe, - METH_VARARGS, + METH_NOARGS, _mysql_thread_safe__doc__ }, -#endif { "server_init", (PyCFunction)_mysql_server_init, From ad935e4ba8c3e4438c952bc417da5d1d0e9d482f Mon Sep 17 00:00:00 2001 From: Vilnis Termanis Date: Tue, 26 Jul 2016 18:43:35 +0100 Subject: [PATCH 032/396] Warning propagation improvements --- MySQLdb/cursors.py | 34 ++++++++++++++++++------------ tests/test_MySQLdb_capabilities.py | 24 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 3e4a0e7b..94828862 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -73,8 +73,7 @@ def __init__(self, connection): self.messages = [] self.errorhandler = connection.errorhandler self._result = None - self._warnings = 0 - self._info = None + self._warnings = None self.rownumber = None def close(self): @@ -128,29 +127,37 @@ def _check_executed(self): def _warning_check(self): from warnings import warn + db = self._get_db() + + # None => warnings not interrogated for current query yet + # 0 => no warnings exists or have been handled already for this query + if self._warnings is None: + self._warnings = db.warning_count() if self._warnings: + # Only propagate warnings for current query once + warning_count = self._warnings + self._warnings = 0 # When there is next result, fetching warnings cause "command # out of sync" error. if self._result and self._result.has_next: - msg = "There are %d MySQL warnings." % (self._warnings,) + msg = "There are %d MySQL warnings." % (warning_count,) self.messages.append(msg) - warn(msg, self.Warning, 3) + warn(self.Warning(0, msg), stacklevel=3) return - warnings = self._get_db().show_warnings() + warnings = db.show_warnings() if warnings: # This is done in two loops in case # Warnings are set to raise exceptions. for w in warnings: self.messages.append((self.Warning, w)) for w in warnings: - msg = w[-1] - if not PY2 and isinstance(msg, bytes): - msg = msg.decode() - warn(msg, self.Warning, 3) - elif self._info: - self.messages.append((self.Warning, self._info)) - warn(self._info, self.Warning, 3) + warn(self.Warning(*w[1:3]), stacklevel=3) + else: + info = db.info() + if info: + self.messages.append((self.Warning, info)) + warn(self.Warning(0, info), stacklevel=3) def nextset(self): """Advance to the next result set. @@ -180,8 +187,7 @@ def _do_get_result(self): self.description = self._result and self._result.describe() or None self.description_flags = self._result and self._result.field_flags() or None self.lastrowid = db.insert_id() - self._warnings = db.warning_count() - self._info = db.info() + self._warnings = None def setinputsizes(self, *args): """Does nothing, required by DB API.""" diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 1ec32f71..0adc4095 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -3,6 +3,8 @@ from datetime import timedelta import unittest import MySQLdb +from MySQLdb.compat import unicode +from MySQLdb import cursors import warnings @@ -155,6 +157,28 @@ def test_reraise_exception(self): return self.fail("Should raise ProgrammingError") + def test_warning_propagation(self): + with warnings.catch_warnings(): + # Ignore all warnings other than MySQLdb generated ones + warnings.simplefilter("ignore") + warnings.simplefilter("error", category=MySQLdb.Warning) + + # verify for both buffered and unbuffered cursor types + for cursor_class in (cursors.Cursor, cursors.SSCursor): + c = self.connection.cursor(cursor_class) + try: + c.execute("SELECT CAST('124b' AS SIGNED)") + c.fetchall() + except MySQLdb.Warning as e: + # Warnings should have errorcode and string message, just like exceptions + self.assertEqual(len(e.args), 2) + self.assertEqual(e.args[0], 1292) + self.assertTrue(isinstance(e.args[1], unicode)) + else: + self.fail("Should raise Warning") + finally: + c.close() + if __name__ == '__main__': if test_MySQLdb.leak_test: From c7d2168939f5601ac592bbae9a99617ef6e567c3 Mon Sep 17 00:00:00 2001 From: "Woo-Jing, Seok" Date: Thu, 28 Jul 2016 18:49:52 +0900 Subject: [PATCH 033/396] add surrogate escape for callproc and test case for that (#104) --- MySQLdb/cursors.py | 2 +- tests/test_MySQLdb_capabilities.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 3e4a0e7b..e3dffc2f 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -347,7 +347,7 @@ def callproc(self, procname, args=()): q = "SET @_%s_%d=%s" % (procname, index, db.literal(arg)) if isinstance(q, unicode): - q = q.encode(db.unicode_literal.charset) + q = q.encode(db.unicode_literal.charset, 'surrogateescape') self._query(q) self.nextset() diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 1ec32f71..dfd509e4 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- import capabilities from datetime import timedelta import unittest @@ -43,7 +44,7 @@ def test_stored_procedures(self): c = self.cursor self.create_table(('pos INT', 'tree CHAR(20)')) c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, - list(enumerate('ash birch cedar larch pine'.split()))) + list(enumerate('ash birch cedar Lärche pine'.split()))) db.commit() c.execute(""" @@ -54,7 +55,7 @@ def test_stored_procedures(self): """ % self.table) db.commit() - c.callproc('test_sp', ('larch',)) + c.callproc('test_sp', ('Lärche',)) rows = c.fetchall() self.assertEqual(len(rows), 1) self.assertEqual(rows[0][0], 3) From 19ad21179c7a20985d1dc961b20514153116671d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 31 Aug 2016 16:20:36 +0900 Subject: [PATCH 034/396] Remove unused import from sample code fixes #107 --- samples/waiter_gevent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/waiter_gevent.py b/samples/waiter_gevent.py index 7ee835f4..698b9158 100644 --- a/samples/waiter_gevent.py +++ b/samples/waiter_gevent.py @@ -2,7 +2,6 @@ """Demo using Gevent with mysqlclient.""" import gevent.hub -import select import MySQLdb From 05fbc500d19cc5959cb998b4a708039d6f3ff26e Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 5 Sep 2016 01:05:12 +0900 Subject: [PATCH 035/396] Always MYSQL_VERSION_ID >= 50007 --- _mysql.c | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/_mysql.c b/_mysql.c index cffde1ef..99507614 100644 --- a/_mysql.c +++ b/_mysql.c @@ -251,7 +251,6 @@ static PyObject *_mysql_server_init( &cmd_args, &groups)) return NULL; -#if MYSQL_VERSION_ID >= 40000 if (cmd_args) { if (!PySequence_Check(cmd_args)) { PyErr_SetString(PyExc_TypeError, @@ -318,7 +317,6 @@ static PyObject *_mysql_server_init( _mysql_Exception(NULL); goto finish; } -#endif ret = Py_None; Py_INCREF(Py_None); _mysql_server_init_done = 1; @@ -336,9 +334,7 @@ static PyObject *_mysql_server_end( PyObject *self, PyObject *args) { if (_mysql_server_init_done) { -#if MYSQL_VERSION_ID >= 40000 mysql_server_end(); -#endif _mysql_server_init_done = 0; Py_INCREF(Py_None); return Py_None; @@ -1063,9 +1059,6 @@ _mysql_escape_string( str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); if (!str) return PyErr_NoMemory(); out = PyBytes_AS_STRING(str); -#if MYSQL_VERSION_ID < 32321 - len = mysql_escape_string(out, in, size); -#else check_server_init(NULL); if (self && PyModule_Check((PyObject*)self)) @@ -1074,7 +1067,6 @@ _mysql_escape_string( len = mysql_real_escape_string(&(self->connection), out, in, size); else len = mysql_escape_string(out, in, size); -#endif if (_PyBytes_Resize(&str, len) < 0) return NULL; return (str); } @@ -1123,15 +1115,12 @@ _mysql_string_literal( return PyErr_NoMemory(); } out = PyBytes_AS_STRING(str); -#if MYSQL_VERSION_ID < 32321 - len = mysql_escape_string(out+1, in, size); -#else check_server_init(NULL); - if (self && self->open) + if (self && self->open) { len = mysql_real_escape_string(&(self->connection), out+1, in, size); - else + } else { len = mysql_escape_string(out+1, in, size); -#endif + } *out = *(out+len+1) = '\''; if (_PyBytes_Resize(&str, len+2) < 0) return NULL; Py_DECREF(s); @@ -1593,8 +1582,6 @@ _mysql_ResultObject_fetch_row( return NULL; } -#if MYSQL_VERSION_ID >= 32303 - static char _mysql_ConnectionObject_change_user__doc__[] = "Changes the user and causes the database specified by db to\n\ become the default (current) database on the connection\n\ @@ -1633,7 +1620,6 @@ _mysql_ConnectionObject_change_user( Py_INCREF(Py_None); return Py_None; } -#endif static char _mysql_ConnectionObject_character_set_name__doc__[] = "Returns the default character set for the current connection.\n\ @@ -1651,7 +1637,6 @@ _mysql_ConnectionObject_character_set_name( return PyString_FromString(s); } -#if MYSQL_VERSION_ID >= 50007 static char _mysql_ConnectionObject_set_character_set__doc__[] = "Sets the default character set for the current connection.\n\ Non-standard.\n\ @@ -1673,7 +1658,6 @@ _mysql_ConnectionObject_set_character_set( Py_INCREF(Py_None); return Py_None; } -#endif #if MYSQL_VERSION_ID >= 50010 static char _mysql_ConnectionObject_get_character_set_info__doc__[] = @@ -2039,11 +2023,7 @@ _mysql_ConnectionObject_shutdown( int r; check_connection(self); Py_BEGIN_ALLOW_THREADS - r = mysql_shutdown(&(self->connection) -#if MYSQL_VERSION_ID >= 40103 - , SHUTDOWN_DEFAULT -#endif - ); + r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); Py_INCREF(Py_None); @@ -2333,14 +2313,12 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { METH_NOARGS, _mysql_ConnectionObject_character_set_name__doc__ }, -#if MYSQL_VERSION_ID >= 50007 { "set_character_set", (PyCFunction)_mysql_ConnectionObject_set_character_set, METH_VARARGS, _mysql_ConnectionObject_set_character_set__doc__ }, -#endif #if MYSQL_VERSION_ID >= 50010 { "get_character_set_info", From a83baad1d0cda46e1619fa737a3cf18cb97a248b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 5 Sep 2016 01:16:12 +0900 Subject: [PATCH 036/396] Use mysql_real_escape_string_quote() if exists. --- _mysql.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/_mysql.c b/_mysql.c index 99507614..ae7306eb 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1063,10 +1063,15 @@ _mysql_escape_string( if (self && PyModule_Check((PyObject*)self)) self = NULL; - if (self && self->open) + if (self && self->open) { +#if MYSQL_VERSION_ID >= 50706 + len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); +#else len = mysql_real_escape_string(&(self->connection), out, in, size); - else +#endif + } else { len = mysql_escape_string(out, in, size); + } if (_PyBytes_Resize(&str, len) < 0) return NULL; return (str); } @@ -1117,7 +1122,11 @@ _mysql_string_literal( out = PyBytes_AS_STRING(str); check_server_init(NULL); if (self && self->open) { +#if MYSQL_VERSION_ID >= 50706 + len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); +#else len = mysql_real_escape_string(&(self->connection), out+1, in, size); +#endif } else { len = mysql_escape_string(out+1, in, size); } From 269d3208ff2707e8063bdb2c7607b28b5769669b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 6 Sep 2016 14:00:12 +0900 Subject: [PATCH 037/396] MySQL Connector/C 6.1.6 doesn't have mysql_real_escape_string_quote (#111) While it's MYSQL_VERSION_ID is 50706 and MySQL 5.7.6 has it. Since MySQL 5.7.6 is old milestone version, just drop supporting it. fixes #110 --- _mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_mysql.c b/_mysql.c index ae7306eb..aa22377d 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1064,7 +1064,7 @@ _mysql_escape_string( if (self && PyModule_Check((PyObject*)self)) self = NULL; if (self && self->open) { -#if MYSQL_VERSION_ID >= 50706 +#if MYSQL_VERSION_ID >= 50707 len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); #else len = mysql_real_escape_string(&(self->connection), out, in, size); @@ -1122,7 +1122,7 @@ _mysql_string_literal( out = PyBytes_AS_STRING(str); check_server_init(NULL); if (self && self->open) { -#if MYSQL_VERSION_ID >= 50706 +#if MYSQL_VERSION_ID >= 50707 len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); #else len = mysql_real_escape_string(&(self->connection), out+1, in, size); From 2617620e2782d0c2f44bf96b32eb57429d6ded5d Mon Sep 17 00:00:00 2001 From: Vilnis Termanis Date: Sun, 11 Sep 2016 11:55:34 +0100 Subject: [PATCH 038/396] Use _binary prefix for bytes/bytearray parameters (#106) --- MySQLdb/__init__.py | 9 +++++++-- MySQLdb/connections.py | 11 +++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index fc414810..9dc8dabc 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -27,6 +27,7 @@ paramstyle = "format" from _mysql import * +from MySQLdb.compat import PY2 from MySQLdb.constants import FIELD_TYPE from MySQLdb.times import Date, Time, Timestamp, \ DateFromTicks, TimeFromTicks, TimestampFromTicks @@ -72,8 +73,12 @@ def test_DBAPISet_set_equality_membership(): def test_DBAPISet_set_inequality_membership(): assert FIELD_TYPE.DATE != STRING -def Binary(x): - return bytes(x) +if PY2: + def Binary(x): + return bytearray(x) +else: + def Binary(x): + return bytes(x) def Connect(*args, **kwargs): """Factory function for connections.Connection.""" diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index d8406db8..e8f6cc48 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -197,7 +197,7 @@ class object, used to create cursors (keyword only) db = proxy(self) def _get_string_literal(): - # Note: string_literal() is called for bytes object on Python 3. + # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) def string_literal(obj, dummy=None): return db.string_literal(obj) return string_literal @@ -213,6 +213,11 @@ def unicode_literal(u, dummy=None): return db.literal(str(u).encode(unicode_literal.charset)) return unicode_literal + def _get_bytes_literal(): + def bytes_literal(obj, dummy=None): + return b'_binary' + db.string_literal(obj) + return bytes_literal + def _get_string_decoder(): def string_decoder(s): return s.decode(string_decoder.charset) @@ -220,6 +225,7 @@ def string_decoder(s): string_literal = _get_string_literal() self.unicode_literal = unicode_literal = _get_unicode_literal() + bytes_literal = _get_bytes_literal() self.string_decoder = string_decoder = _get_string_decoder() if not charset: charset = self.character_set_name() @@ -234,7 +240,8 @@ def string_decoder(s): self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) - self.encoders[bytes] = string_literal + self.encoders[bytes] = string_literal if PY2 else bytes_literal + self.encoders[bytearray] = bytes_literal self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: From db1bb2ccb1971311095b9a0c10f13c9251be4af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 12 Sep 2016 00:31:28 +0300 Subject: [PATCH 039/396] Spelling fixes (#112) --- MySQLdb/cursors.py | 4 ++-- _mysql.c | 2 +- doc/FAQ.rst | 2 +- doc/user_guide.rst | 4 ++-- tests/dbapi20.py | 10 +++++----- tests/test_MySQLdb_dbapi20.py | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index ee493f2c..9ebf0a14 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -23,7 +23,7 @@ #: Regular expression for :meth:`Cursor.executemany`. -#: executemany only suports simple bulk insert. +#: executemany only supports simple bulk insert. #: You can use it to load large dataset. RE_INSERT_VALUES = re.compile( r"\s*((?:INSERT|REPLACE)\s.+\sVALUES?\s+)" + @@ -475,7 +475,7 @@ class CursorUseResultMixIn(object): """This is a MixIn class which causes the result set to be stored in the server and sent row-by-row to client side, i.e. it uses mysql_use_result(). You MUST retrieve the entire result set and - close() the cursor before additional queries can be peformed on + close() the cursor before additional queries can be performed on the connection.""" _defer_warnings = True diff --git a/_mysql.c b/_mysql.c index aa22377d..dea4d547 100644 --- a/_mysql.c +++ b/_mysql.c @@ -2508,7 +2508,7 @@ static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { T_UINT, offsetof(_mysql_ConnectionObject,connection.server_capabilities), READONLY, - "Capabilites of server; consult MySQLdb.constants.CLIENT" + "Capabilities of server; consult MySQLdb.constants.CLIENT" }, { "port", diff --git a/doc/FAQ.rst b/doc/FAQ.rst index ab06a212..59c6b93d 100644 --- a/doc/FAQ.rst +++ b/doc/FAQ.rst @@ -20,7 +20,7 @@ threadsafe = False. This almost always mean you don't have development packages installed. On some systems, C headers for various things (like MySQL) -are distributed as a seperate package. You'll need to figure out +are distributed as a separate package. You'll need to figure out what that is and install it, but often the name ends with -devel. Another possibility: Some older versions of mysql_config behave oddly diff --git a/doc/user_guide.rst b/doc/user_guide.rst index b39a230a..0ce88aee 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -441,7 +441,7 @@ conv is a function as above. The sequence is tested until the flags on the field match those of the first value. If both values are None, then the default conversion is done. Presently this - is only used to distinquish TEXT and BLOB columns. + is only used to distinguish TEXT and BLOB columns. If the key is a Python type or class, then the value is a callable Python object (usually a function) taking two arguments @@ -615,7 +615,7 @@ Using and extending ------------------- In general, it is probably wise to not directly interact with the DB -API except for small applicatons. Databases, even SQL databases, vary +API except for small applications. Databases, even SQL databases, vary widely in capabilities and may have non-standard features. The DB API does a good job of providing a reasonably portable interface but some methods are non-portable. Specifically, the parameters accepted by diff --git a/tests/dbapi20.py b/tests/dbapi20.py index e1326d1b..19109f4d 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -52,9 +52,9 @@ # - Now a subclass of TestCase, to avoid requiring the driver stub # to use multiple inheritance # - Reversed the polarity of buggy test in test_description -# - Test exception heirarchy correctly +# - Test exception hierarchy correctly # - self.populate is now self._populate(), so if a driver stub -# overrides self.ddl1 this change propogates +# overrides self.ddl1 this change propagates # - VARCHAR columns now have a width, which will hopefully make the # DDL even more portible (this will be reversed if it causes more problems) # - cursor.rowcount being checked after various execute and fetchXXX methods @@ -176,7 +176,7 @@ def test_paramstyle(self): def test_Exceptions(self): # Make sure required exceptions exist, and are in the - # defined heirarchy. + # defined hierarchy. self.assertTrue(issubclass(self.driver.Warning,Exception)) self.assertTrue(issubclass(self.driver.Error,Exception)) self.assertTrue( @@ -485,7 +485,7 @@ def test_fetchone(self): self.assertRaises(self.driver.Error,cur.fetchone) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows self.executeDDL1(cur) self.assertRaises(self.driver.Error,cur.fetchone) @@ -497,7 +497,7 @@ def test_fetchone(self): self.assertTrue(cur.rowcount in (-1,0)) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( self.table_prefix )) diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 3fef3f5f..2274c454 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -80,7 +80,7 @@ def test_fetchone(self): self.assertRaises(self.driver.Error,cur.fetchone) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows self.executeDDL1(cur) ## self.assertRaises(self.driver.Error,cur.fetchone) @@ -92,7 +92,7 @@ def test_fetchone(self): self.assertTrue(cur.rowcount in (-1,0)) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( self.table_prefix )) From dd9929c0e799633f88342216fb34b412c7f454b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 12 Sep 2016 00:34:25 +0300 Subject: [PATCH 040/396] Update error constants (#113) * Add CR and ER main methods for updating from connector-c headers * Sync CR and ER with MySQL/MariaDB connector C 6.1.6/2.3.1 --- MySQLdb/constants/CR.py | 76 ++++- MySQLdb/constants/ER.py | 597 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 670 insertions(+), 3 deletions(-) diff --git a/MySQLdb/constants/CR.py b/MySQLdb/constants/CR.py index 249dfec9..1b047243 100644 --- a/MySQLdb/constants/CR.py +++ b/MySQLdb/constants/CR.py @@ -5,8 +5,35 @@ """ +if __name__ == "__main__": + """ + Usage: python CR.py [/path/to/mysql/errmsg.h ...] >> CR.py + """ + import fileinput, re + data = {} + error_last = None + for line in fileinput.input(): + line = re.sub(r'/\*.*?\*/', '', line) + m = re.match(r'^\s*#define\s+CR_([A-Z0-9_]+)\s+(\d+)(\s.*|$)', line) + if m: + name = m.group(1) + value = int(m.group(2)) + if name == 'ERROR_LAST': + if error_last is None or error_last < value: + error_last = value + continue + if value not in data: + data[value] = set() + data[value].add(name) + for value, names in sorted(data.items()): + for name in sorted(names): + print('%s = %s' % (name, value)) + if error_last is not None: + print('ERROR_LAST = %s' % error_last) + + +ERROR_FIRST = 2000 MIN_ERROR = 2000 -MAX_ERROR = 2999 UNKNOWN_ERROR = 2000 SOCKET_CREATE_ERROR = 2001 CONNECTION_ERROR = 2002 @@ -28,3 +55,50 @@ NAMEDPIPESETSTATE_ERROR = 2018 CANT_READ_CHARSET = 2019 NET_PACKET_TOO_LARGE = 2020 +EMBEDDED_CONNECTION = 2021 +PROBE_SLAVE_STATUS = 2022 +PROBE_SLAVE_HOSTS = 2023 +PROBE_SLAVE_CONNECT = 2024 +PROBE_MASTER_CONNECT = 2025 +SSL_CONNECTION_ERROR = 2026 +MALFORMED_PACKET = 2027 +WRONG_LICENSE = 2028 +NULL_POINTER = 2029 +NO_PREPARE_STMT = 2030 +PARAMS_NOT_BOUND = 2031 +DATA_TRUNCATED = 2032 +NO_PARAMETERS_EXISTS = 2033 +INVALID_PARAMETER_NO = 2034 +INVALID_BUFFER_USE = 2035 +UNSUPPORTED_PARAM_TYPE = 2036 +SHARED_MEMORY_CONNECTION = 2037 +SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038 +SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039 +SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040 +SHARED_MEMORY_CONNECT_MAP_ERROR = 2041 +SHARED_MEMORY_FILE_MAP_ERROR = 2042 +SHARED_MEMORY_MAP_ERROR = 2043 +SHARED_MEMORY_EVENT_ERROR = 2044 +SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045 +SHARED_MEMORY_CONNECT_SET_ERROR = 2046 +CONN_UNKNOW_PROTOCOL = 2047 +INVALID_CONN_HANDLE = 2048 +SECURE_AUTH = 2049 +UNUSED_1 = 2049 +FETCH_CANCELED = 2050 +NO_DATA = 2051 +NO_STMT_METADATA = 2052 +NO_RESULT_SET = 2053 +NOT_IMPLEMENTED = 2054 +SERVER_LOST_EXTENDED = 2055 +STMT_CLOSED = 2056 +NEW_STMT_METADATA = 2057 +ALREADY_CONNECTED = 2058 +AUTH_PLUGIN_CANNOT_LOAD = 2058 +ALREADY_CONNECTED = 2059 +AUTH_PLUGIN_CANNOT_LOAD = 2059 +DUPLICATE_CONNECTION_ATTR = 2060 +PLUGIN_FUNCTION_NOT_SUPPORTED = 2060 +AUTH_PLUGIN_ERR = 2061 +MAX_ERROR = 2999 +ERROR_LAST = 2061 diff --git a/MySQLdb/constants/ER.py b/MySQLdb/constants/ER.py index ed45f3a1..59db2e1f 100644 --- a/MySQLdb/constants/ER.py +++ b/MySQLdb/constants/ER.py @@ -5,6 +5,36 @@ """ +if __name__ == "__main__": + """ + Usage: python ER.py [/path/to/mysql/mysqld_error.h ...] >> ER.py + """ + import fileinput, re + data = {} + error_last = None + for line in fileinput.input(): + line = re.sub(r'/\*.*?\*/', '', line) + m = re.match(r'^\s*#define\s+((ER|WARN)_[A-Z0-9_]+)\s+(\d+)\s*', line) + if m: + name = m.group(1) + if name.startswith('ER_'): + name = name[3:] + value = int(m.group(3)) + if name == 'ERROR_LAST': + if error_last is None or error_last < value: + error_last = value + continue + if value not in data: + data[value] = set() + data[value].add(name) + for value, names in sorted(data.items()): + for name in sorted(names): + print('%s = %s' % (name, value)) + if error_last is not None: + print('ERROR_LAST = %s' % error_last) + + +ERROR_FIRST = 1000 HASHCHK = 1000 NISAMCHK = 1001 NO = 1002 @@ -99,6 +129,7 @@ CANT_DROP_FIELD_OR_KEY = 1091 INSERT_INFO = 1092 INSERT_TABLE_USED = 1093 +UPDATE_TABLE_USED = 1093 NO_SUCH_THREAD = 1094 KILL_DENIED_ERROR = 1095 NO_TABLES_USED = 1096 @@ -156,7 +187,9 @@ NOT_ALLOWED_COMMAND = 1148 SYNTAX_ERROR = 1149 DELAYED_CANT_CHANGE_LOCK = 1150 +UNUSED1 = 1150 TOO_MANY_DELAYED_THREADS = 1151 +UNUSED2 = 1151 ABORTING_CONNECTION = 1152 NET_PACKET_TOO_LARGE = 1153 NET_READ_ERROR_FROM_PIPE = 1154 @@ -171,6 +204,7 @@ TABLE_CANT_HANDLE_BLOB = 1163 TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 DELAYED_INSERT_TABLE_LOCKED = 1165 +UNUSED3 = 1165 WRONG_COLUMN_NAME = 1166 WRONG_KEY_COLUMN = 1167 WRONG_MRG_TABLE = 1168 @@ -220,6 +254,7 @@ UNION_TABLES_IN_DIFFERENT_DIR = 1212 LOCK_DEADLOCK = 1213 TABLE_CANT_HANDLE_FT = 1214 +TABLE_CANT_HANDLE_FULLTEXT = 1214 CANNOT_ADD_FOREIGN = 1215 NO_REFERENCED_ROW = 1216 ROW_IS_REFERENCED = 1217 @@ -463,5 +498,563 @@ OLD_FILE_FORMAT = 1455 SP_RECURSION_LIMIT = 1456 SP_PROC_TABLE_CORRUPT = 1457 -ERROR_LAST = 1457 - +SP_WRONG_NAME = 1458 +TABLE_NEEDS_UPGRADE = 1459 +SP_NO_AGGREGATE = 1460 +MAX_PREPARED_STMT_COUNT_REACHED = 1461 +VIEW_RECURSIVE = 1462 +NON_GROUPING_FIELD_USED = 1463 +TABLE_CANT_HANDLE_SPKEYS = 1464 +NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 +REMOVED_SPACES = 1466 +AUTOINC_READ_FAILED = 1467 +USERNAME = 1468 +HOSTNAME = 1469 +WRONG_STRING_LENGTH = 1470 +NON_INSERTABLE_TABLE = 1471 +ADMIN_WRONG_MRG_TABLE = 1472 +TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 +NAME_BECOMES_EMPTY = 1474 +AMBIGUOUS_FIELD_TERM = 1475 +FOREIGN_SERVER_EXISTS = 1476 +FOREIGN_SERVER_DOESNT_EXIST = 1477 +ILLEGAL_HA_CREATE_OPTION = 1478 +PARTITION_REQUIRES_VALUES_ERROR = 1479 +PARTITION_WRONG_VALUES_ERROR = 1480 +PARTITION_MAXVALUE_ERROR = 1481 +PARTITION_SUBPARTITION_ERROR = 1482 +PARTITION_SUBPART_MIX_ERROR = 1483 +PARTITION_WRONG_NO_PART_ERROR = 1484 +PARTITION_WRONG_NO_SUBPART_ERROR = 1485 +WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 +NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 +FIELD_NOT_FOUND_PART_ERROR = 1488 +LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 +INCONSISTENT_PARTITION_INFO_ERROR = 1490 +PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 +PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 +RANGE_NOT_INCREASING_ERROR = 1493 +INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 +MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 +PARTITION_ENTRY_ERROR = 1496 +MIX_HANDLER_ERROR = 1497 +PARTITION_NOT_DEFINED_ERROR = 1498 +TOO_MANY_PARTITIONS_ERROR = 1499 +SUBPARTITION_ERROR = 1500 +CANT_CREATE_HANDLER_FILE = 1501 +BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 +UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 +NO_PARTS_ERROR = 1504 +PARTITION_MGMT_ON_NONPARTITIONED = 1505 +FOREIGN_KEY_ON_PARTITIONED = 1506 +DROP_PARTITION_NON_EXISTENT = 1507 +DROP_LAST_PARTITION = 1508 +COALESCE_ONLY_ON_HASH_PARTITION = 1509 +REORG_HASH_ONLY_ON_SAME_NO = 1510 +REORG_NO_PARAM_ERROR = 1511 +ONLY_ON_RANGE_LIST_PARTITION = 1512 +ADD_PARTITION_SUBPART_ERROR = 1513 +ADD_PARTITION_NO_NEW_PARTITION = 1514 +COALESCE_PARTITION_NO_PARTITION = 1515 +REORG_PARTITION_NOT_EXIST = 1516 +SAME_NAME_PARTITION = 1517 +NO_BINLOG_ERROR = 1518 +CONSECUTIVE_REORG_PARTITIONS = 1519 +REORG_OUTSIDE_RANGE = 1520 +PARTITION_FUNCTION_FAILURE = 1521 +PART_STATE_ERROR = 1522 +LIMITED_PART_RANGE = 1523 +PLUGIN_IS_NOT_LOADED = 1524 +WRONG_VALUE = 1525 +NO_PARTITION_FOR_GIVEN_VALUE = 1526 +FILEGROUP_OPTION_ONLY_ONCE = 1527 +CREATE_FILEGROUP_FAILED = 1528 +DROP_FILEGROUP_FAILED = 1529 +TABLESPACE_AUTO_EXTEND_ERROR = 1530 +WRONG_SIZE_NUMBER = 1531 +SIZE_OVERFLOW_ERROR = 1532 +ALTER_FILEGROUP_FAILED = 1533 +BINLOG_ROW_LOGGING_FAILED = 1534 +BINLOG_ROW_WRONG_TABLE_DEF = 1535 +BINLOG_ROW_RBR_TO_SBR = 1536 +EVENT_ALREADY_EXISTS = 1537 +EVENT_STORE_FAILED = 1538 +EVENT_DOES_NOT_EXIST = 1539 +EVENT_CANT_ALTER = 1540 +EVENT_DROP_FAILED = 1541 +EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 +EVENT_ENDS_BEFORE_STARTS = 1543 +EVENT_EXEC_TIME_IN_THE_PAST = 1544 +EVENT_OPEN_TABLE_FAILED = 1545 +EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 +COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 +OBSOLETE_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 +CANNOT_LOAD_FROM_TABLE = 1548 +OBSOLETE_CANNOT_LOAD_FROM_TABLE = 1548 +EVENT_CANNOT_DELETE = 1549 +EVENT_COMPILE_ERROR = 1550 +EVENT_SAME_NAME = 1551 +EVENT_DATA_TOO_LONG = 1552 +DROP_INDEX_FK = 1553 +WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 +CANT_WRITE_LOCK_LOG_TABLE = 1555 +CANT_LOCK_LOG_TABLE = 1556 +FOREIGN_DUPLICATE_KEY = 1557 +FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 +COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 +TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 +STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 +NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 +PARTITION_NO_TEMPORARY = 1562 +PARTITION_CONST_DOMAIN_ERROR = 1563 +PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 +DDL_LOG_ERROR = 1565 +NULL_IN_VALUES_LESS_THAN = 1566 +WRONG_PARTITION_NAME = 1567 +CANT_CHANGE_TX_CHARACTERISTICS = 1568 +CANT_CHANGE_TX_ISOLATION = 1568 +DUP_ENTRY_AUTOINCREMENT_CASE = 1569 +EVENT_MODIFY_QUEUE_ERROR = 1570 +EVENT_SET_VAR_ERROR = 1571 +PARTITION_MERGE_ERROR = 1572 +CANT_ACTIVATE_LOG = 1573 +RBR_NOT_AVAILABLE = 1574 +BASE64_DECODE_ERROR = 1575 +EVENT_RECURSION_FORBIDDEN = 1576 +EVENTS_DB_ERROR = 1577 +ONLY_INTEGERS_ALLOWED = 1578 +UNSUPORTED_LOG_ENGINE = 1579 +BAD_LOG_STATEMENT = 1580 +CANT_RENAME_LOG_TABLE = 1581 +WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 +WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 +WRONG_PARAMETERS_TO_STORED_FCT = 1584 +NATIVE_FCT_NAME_COLLISION = 1585 +DUP_ENTRY_WITH_KEY_NAME = 1586 +BINLOG_PURGE_EMFILE = 1587 +EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 +EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 +SLAVE_INCIDENT = 1590 +NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 +BINLOG_UNSAFE_STATEMENT = 1592 +SLAVE_FATAL_ERROR = 1593 +SLAVE_RELAY_LOG_READ_FAILURE = 1594 +SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 +SLAVE_CREATE_EVENT_FAILURE = 1596 +SLAVE_MASTER_COM_FAILURE = 1597 +BINLOG_LOGGING_IMPOSSIBLE = 1598 +VIEW_NO_CREATION_CTX = 1599 +VIEW_INVALID_CREATION_CTX = 1600 +SR_INVALID_CREATION_CTX = 1601 +TRG_CORRUPTED_FILE = 1602 +TRG_NO_CREATION_CTX = 1603 +TRG_INVALID_CREATION_CTX = 1604 +EVENT_INVALID_CREATION_CTX = 1605 +TRG_CANT_OPEN_TABLE = 1606 +CANT_CREATE_SROUTINE = 1607 +NEVER_USED = 1608 +NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 +SLAVE_CORRUPT_EVENT = 1610 +LOAD_DATA_INVALID_COLUMN = 1611 +LOG_PURGE_NO_FILE = 1612 +XA_RBTIMEOUT = 1613 +XA_RBDEADLOCK = 1614 +NEED_REPREPARE = 1615 +DELAYED_NOT_SUPPORTED = 1616 +WARN_NO_MASTER_INFO = 1617 +WARN_OPTION_IGNORED = 1618 +PLUGIN_DELETE_BUILTIN = 1619 +WARN_PLUGIN_DELETE_BUILTIN = 1619 +WARN_PLUGIN_BUSY = 1620 +VARIABLE_IS_READONLY = 1621 +WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 +SLAVE_HEARTBEAT_FAILURE = 1623 +SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 +NDB_REPLICATION_SCHEMA_ERROR = 1625 +CONFLICT_FN_PARSE_ERROR = 1626 +EXCEPTIONS_WRITE_ERROR = 1627 +TOO_LONG_TABLE_COMMENT = 1628 +TOO_LONG_FIELD_COMMENT = 1629 +FUNC_INEXISTENT_NAME_COLLISION = 1630 +DATABASE_NAME = 1631 +TABLE_NAME = 1632 +PARTITION_NAME = 1633 +SUBPARTITION_NAME = 1634 +TEMPORARY_NAME = 1635 +RENAMED_NAME = 1636 +TOO_MANY_CONCURRENT_TRXS = 1637 +WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 +DEBUG_SYNC_TIMEOUT = 1639 +DEBUG_SYNC_HIT_LIMIT = 1640 +DUP_SIGNAL_SET = 1641 +SIGNAL_WARN = 1642 +SIGNAL_NOT_FOUND = 1643 +SIGNAL_EXCEPTION = 1644 +RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 +SIGNAL_BAD_CONDITION_TYPE = 1646 +WARN_COND_ITEM_TRUNCATED = 1647 +COND_ITEM_TOO_LONG = 1648 +UNKNOWN_LOCALE = 1649 +SLAVE_IGNORE_SERVER_IDS = 1650 +QUERY_CACHE_DISABLED = 1651 +SAME_NAME_PARTITION_FIELD = 1652 +PARTITION_COLUMN_LIST_ERROR = 1653 +WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 +TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 +MAXVALUE_IN_VALUES_IN = 1656 +TOO_MANY_VALUES_ERROR = 1657 +ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 +FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 +PARTITION_FIELDS_TOO_LONG = 1660 +BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 +BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 +BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 +BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 +BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 +BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 +BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 +BINLOG_UNSAFE_LIMIT = 1668 +BINLOG_UNSAFE_INSERT_DELAYED = 1669 +UNUSED4 = 1669 +BINLOG_UNSAFE_SYSTEM_TABLE = 1670 +BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 +BINLOG_UNSAFE_UDF = 1672 +BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 +BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 +BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 +MESSAGE_AND_STATEMENT = 1676 +SLAVE_CONVERSION_FAILED = 1677 +SLAVE_CANT_CREATE_CONVERSION = 1678 +INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 +PATH_LENGTH = 1680 +WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 +WRONG_NATIVE_TABLE_STRUCTURE = 1682 +WRONG_PERFSCHEMA_USAGE = 1683 +WARN_I_S_SKIPPED_TABLE = 1684 +INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 +STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 +SPATIAL_MUST_HAVE_GEOM_COL = 1687 +TOO_LONG_INDEX_COMMENT = 1688 +LOCK_ABORTED = 1689 +DATA_OUT_OF_RANGE = 1690 +WRONG_SPVAR_TYPE_IN_LIMIT = 1691 +BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 +BINLOG_UNSAFE_MIXED_STATEMENT = 1693 +INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 +STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 +FAILED_READ_FROM_PAR_FILE = 1696 +VALUES_IS_NOT_INT_TYPE_ERROR = 1697 +ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 +SET_PASSWORD_AUTH_PLUGIN = 1699 +GRANT_PLUGIN_USER_EXISTS = 1700 +TRUNCATE_ILLEGAL_FK = 1701 +PLUGIN_IS_PERMANENT = 1702 +SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 +SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 +STMT_CACHE_FULL = 1705 +MULTI_UPDATE_KEY_CONFLICT = 1706 +TABLE_NEEDS_REBUILD = 1707 +WARN_OPTION_BELOW_LIMIT = 1708 +INDEX_COLUMN_TOO_LONG = 1709 +ERROR_IN_TRIGGER_BODY = 1710 +ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 +INDEX_CORRUPT = 1712 +UNDO_RECORD_TOO_BIG = 1713 +BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 +BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 +BINLOG_UNSAFE_REPLACE_SELECT = 1716 +BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 +BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 +BINLOG_UNSAFE_UPDATE_IGNORE = 1719 +PLUGIN_NO_UNINSTALL = 1720 +PLUGIN_NO_INSTALL = 1721 +BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 +BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 +BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 +TABLE_IN_FK_CHECK = 1725 +UNSUPPORTED_ENGINE = 1726 +UNUSED_1 = 1726 +BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 +CANNOT_LOAD_FROM_TABLE_V2 = 1728 +LAST_MYSQL_ERROR_MESSAGE = 1728 +MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 +ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 +PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 +PARTITION_EXCHANGE_PART_TABLE = 1732 +PARTITION_EXCHANGE_TEMP_TABLE = 1733 +PARTITION_INSTEAD_OF_SUBPARTITION = 1734 +UNKNOWN_PARTITION = 1735 +TABLES_DIFFERENT_METADATA = 1736 +ROW_DOES_NOT_MATCH_PARTITION = 1737 +BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 +WARN_INDEX_NOT_APPLICABLE = 1739 +PARTITION_EXCHANGE_FOREIGN_KEY = 1740 +NO_SUCH_KEY_VALUE = 1741 +RPL_INFO_DATA_TOO_LONG = 1742 +NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 +BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 +BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 +CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 +PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 +ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 +NO_SUCH_PARTITION__UNUSED = 1749 +CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 +WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 +WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 +MTS_FEATURE_IS_NOT_SUPPORTED = 1753 +MTS_UPDATED_DBS_GREATER_MAX = 1754 +MTS_CANT_PARALLEL = 1755 +MTS_INCONSISTENT_DATA = 1756 +FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 +DA_INVALID_CONDITION_NUMBER = 1758 +INSECURE_PLAIN_TEXT = 1759 +INSECURE_CHANGE_MASTER = 1760 +FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 +FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 +SQLTHREAD_WITH_SECURE_SLAVE = 1763 +TABLE_HAS_NO_FT = 1764 +VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 +VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 +GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 +CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL = 1768 +SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 +GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 +SKIPPING_LOGGED_TRANSACTION = 1771 +MALFORMED_GTID_SET_SPECIFICATION = 1772 +MALFORMED_GTID_SET_ENCODING = 1773 +MALFORMED_GTID_SPECIFICATION = 1774 +GNO_EXHAUSTED = 1775 +BAD_SLAVE_AUTO_POSITION = 1776 +AUTO_POSITION_REQUIRES_GTID_MODE_ON = 1777 +CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 +GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 +GTID_MODE_REQUIRES_BINLOG = 1780 +CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 +CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 +CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 +FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF = 1784 +GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 +GTID_UNSAFE_CREATE_SELECT = 1786 +GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787 +GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 +MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 +CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 +UNKNOWN_EXPLAIN_FORMAT = 1791 +CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 +TOO_LONG_TABLE_PARTITION_COMMENT = 1793 +SLAVE_CONFIGURATION = 1794 +INNODB_FT_LIMIT = 1795 +INNODB_NO_FT_TEMP_TABLE = 1796 +INNODB_FT_WRONG_DOCID_COLUMN = 1797 +INNODB_FT_WRONG_DOCID_INDEX = 1798 +INNODB_ONLINE_LOG_TOO_BIG = 1799 +UNKNOWN_ALTER_ALGORITHM = 1800 +UNKNOWN_ALTER_LOCK = 1801 +MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 +MTS_RECOVERY_FAILURE = 1803 +MTS_RESET_WORKERS = 1804 +COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 +SLAVE_SILENT_RETRY_TRANSACTION = 1806 +DISCARD_FK_CHECKS_RUNNING = 1807 +TABLE_SCHEMA_MISMATCH = 1808 +TABLE_IN_SYSTEM_TABLESPACE = 1809 +IO_READ_ERROR = 1810 +IO_WRITE_ERROR = 1811 +TABLESPACE_MISSING = 1812 +TABLESPACE_EXISTS = 1813 +TABLESPACE_DISCARDED = 1814 +INTERNAL_ERROR = 1815 +INNODB_IMPORT_ERROR = 1816 +INNODB_INDEX_CORRUPT = 1817 +INVALID_YEAR_COLUMN_LENGTH = 1818 +NOT_VALID_PASSWORD = 1819 +MUST_CHANGE_PASSWORD = 1820 +FK_NO_INDEX_CHILD = 1821 +FK_NO_INDEX_PARENT = 1822 +FK_FAIL_ADD_SYSTEM = 1823 +FK_CANNOT_OPEN_PARENT = 1824 +FK_INCORRECT_OPTION = 1825 +FK_DUP_NAME = 1826 +PASSWORD_FORMAT = 1827 +FK_COLUMN_CANNOT_DROP = 1828 +FK_COLUMN_CANNOT_DROP_CHILD = 1829 +FK_COLUMN_NOT_NULL = 1830 +DUP_INDEX = 1831 +FK_COLUMN_CANNOT_CHANGE = 1832 +FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 +UNUSED5 = 1834 +MALFORMED_PACKET = 1835 +READ_ONLY_MODE = 1836 +GTID_NEXT_TYPE_UNDEFINED_GROUP = 1837 +VARIABLE_NOT_SETTABLE_IN_SP = 1838 +CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 +CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 +CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 +GTID_PURGED_WAS_CHANGED = 1842 +GTID_EXECUTED_WAS_CHANGED = 1843 +BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 +ALTER_OPERATION_NOT_SUPPORTED = 1845 +ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 +ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 +ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 +ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 +ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 +ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 +UNUSED6 = 1852 +ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 +ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 +ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 +ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 +ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 +SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 +DUP_UNKNOWN_IN_INDEX = 1859 +IDENT_CAUSES_TOO_LONG_PATH = 1860 +ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 +MUST_CHANGE_PASSWORD_LOGIN = 1862 +ROW_IN_WRONG_PARTITION = 1863 +MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 +INNODB_NO_FT_USES_PARSER = 1865 +BINLOG_LOGICAL_CORRUPTION = 1866 +WARN_PURGE_LOG_IN_USE = 1867 +WARN_PURGE_LOG_IS_ACTIVE = 1868 +AUTO_INCREMENT_CONFLICT = 1869 +WARN_ON_BLOCKHOLE_IN_RBR = 1870 +SLAVE_MI_INIT_REPOSITORY = 1871 +SLAVE_RLI_INIT_REPOSITORY = 1872 +ACCESS_DENIED_CHANGE_USER_ERROR = 1873 +INNODB_READ_ONLY = 1874 +STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875 +STOP_SLAVE_IO_THREAD_TIMEOUT = 1876 +TABLE_CORRUPT = 1877 +TEMP_FILE_WRITE_FAILURE = 1878 +INNODB_FT_AUX_NOT_HEX_ID = 1879 +OLD_TEMPORALS_UPGRADED = 1880 +INNODB_FORCED_RECOVERY = 1881 +AES_INVALID_IV = 1882 +PLUGIN_CANNOT_BE_UNINSTALLED = 1883 +GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_GTID_GROUP = 1884 +FILE_CORRUPT = 1885 +ERROR_ON_MASTER = 1886 +INCONSISTENT_ERROR = 1887 +STORAGE_ENGINE_NOT_LOADED = 1888 +GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 1889 +WARN_LEGACY_SYNTAX_CONVERTED = 1890 +BINLOG_UNSAFE_FULLTEXT_PLUGIN = 1891 +CANNOT_DISCARD_TEMPORARY_TABLE = 1892 +FK_DEPTH_EXCEEDED = 1893 +COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 1894 +WARN_TRIGGER_DOESNT_HAVE_CREATED = 1895 +REFERENCED_TRG_DOES_NOT_EXIST = 1896 +EXPLAIN_NOT_SUPPORTED = 1897 +INVALID_FIELD_SIZE = 1898 +MISSING_HA_CREATE_OPTION = 1899 +ENGINE_OUT_OF_MEMORY = 1900 +VCOL_BASED_ON_VCOL = 1900 +PASSWORD_EXPIRE_ANONYMOUS_USER = 1901 +VIRTUAL_COLUMN_FUNCTION_IS_NOT_ALLOWED = 1901 +DATA_CONVERSION_ERROR_FOR_VIRTUAL_COLUMN = 1902 +SLAVE_SQL_THREAD_MUST_STOP = 1902 +NO_FT_MATERIALIZED_SUBQUERY = 1903 +PRIMARY_KEY_BASED_ON_VIRTUAL_COLUMN = 1903 +INNODB_UNDO_LOG_FULL = 1904 +KEY_BASED_ON_GENERATED_VIRTUAL_COLUMN = 1904 +INVALID_ARGUMENT_FOR_LOGARITHM = 1905 +WRONG_FK_OPTION_FOR_VIRTUAL_COLUMN = 1905 +SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 1906 +WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN = 1906 +UNSUPPORTED_ACTION_ON_VIRTUAL_COLUMN = 1907 +WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 1907 +CONST_EXPR_IN_VCOL = 1908 +WARN_ONLY_MASTER_LOG_FILE_NO_POS = 1908 +QUERY_TIMEOUT = 1909 +ROW_EXPR_FOR_VCOL = 1909 +NON_RO_SELECT_DISABLE_TIMER = 1910 +UNSUPPORTED_ENGINE_FOR_VIRTUAL_COLUMNS = 1910 +DUP_LIST_ENTRY = 1911 +UNKNOWN_OPTION = 1911 +BAD_OPTION_VALUE = 1912 +SQL_MODE_NO_EFFECT = 1912 +AGGREGATE_ORDER_FOR_UNION = 1913 +NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1913 +AGGREGATE_ORDER_NON_AGG_QUERY = 1914 +BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1914 +CANT_DO_ONLINE = 1915 +SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 1915 +DATA_OVERFLOW = 1916 +DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER = 1916 +DATA_TRUNCATED = 1917 +SERVER_OFFLINE_MODE = 1917 +BAD_DATA = 1918 +GIS_DIFFERENT_SRIDS = 1918 +DYN_COL_WRONG_FORMAT = 1919 +GIS_UNSUPPORTED_ARGUMENT = 1919 +DYN_COL_IMPLEMENTATION_LIMIT = 1920 +GIS_UNKNOWN_ERROR = 1920 +DYN_COL_DATA = 1921 +GIS_UNKNOWN_EXCEPTION = 1921 +DYN_COL_WRONG_CHARSET = 1922 +GIS_INVALID_DATA = 1922 +BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 1923 +ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES = 1923 +BOOST_GEOMETRY_CENTROID_EXCEPTION = 1924 +QUERY_CACHE_IS_DISABLED = 1924 +BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 1925 +QUERY_CACHE_IS_GLOBALY_DISABLED = 1925 +BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 1926 +VIEW_ORDERBY_IGNORED = 1926 +BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 1927 +CONNECTION_KILLED = 1927 +BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 1928 +INTERNAL_ERROR = 1928 +INSIDE_TRANSACTION_PREVENTS_SWITCH_SKIP_REPLICATION = 1929 +STD_BAD_ALLOC_ERROR = 1929 +STD_DOMAIN_ERROR = 1930 +STORED_FUNCTION_PREVENTS_SWITCH_SKIP_REPLICATION = 1930 +QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT = 1931 +STD_LENGTH_ERROR = 1931 +NO_SUCH_TABLE_IN_ENGINE = 1932 +STD_INVALID_ARGUMENT = 1932 +GEOMETRY_SRID_MISMATCH = 1933 +STD_OUT_OF_RANGE_ERROR = 1933 +NO_SUCH_SPATIAL_REF_ID = 1934 +STD_OVERFLOW_ERROR = 1934 +STD_RANGE_ERROR = 1935 +STD_UNDERFLOW_ERROR = 1936 +STD_LOGIC_ERROR = 1937 +STD_RUNTIME_ERROR = 1938 +STD_UNKNOWN_EXCEPTION = 1939 +GIS_DATA_WRONG_ENDIANESS = 1940 +CHANGE_MASTER_PASSWORD_LENGTH = 1941 +USER_LOCK_WRONG_NAME = 1942 +USER_LOCK_DEADLOCK = 1943 +REPLACE_INACCESSIBLE_ROWS = 1944 +ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 1945 +ILLEGAL_USER_VAR = 1946 +GTID_MODE_OFF = 1947 +UNSUPPORTED_BY_REPLICATION_THREAD = 1948 +INCORRECT_TYPE = 1949 +FIELD_IN_ORDER_NOT_SELECT = 1950 +AGGREGATE_IN_ORDER_NOT_SELECT = 1951 +INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 1952 +NET_OK_PACKET_TOO_LARGE = 1953 +INVALID_JSON_DATA = 1954 +INVALID_GEOJSON_MISSING_MEMBER = 1955 +INVALID_GEOJSON_WRONG_TYPE = 1956 +INVALID_GEOJSON_UNSPECIFIED = 1957 +DIMENSION_UNSUPPORTED = 1958 +SLAVE_CHANNEL_DOES_NOT_EXIST = 1959 +SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 1960 +SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 1961 +SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 1962 +SLAVE_CHANNEL_DELETE = 1963 +SLAVE_MULTIPLE_CHANNELS_CMD = 1964 +SLAVE_MAX_CHANNELS_EXCEEDED = 1965 +SLAVE_CHANNEL_MUST_STOP = 1966 +SLAVE_CHANNEL_NOT_RUNNING = 1967 +SLAVE_CHANNEL_WAS_RUNNING = 1968 +SLAVE_CHANNEL_WAS_NOT_RUNNING = 1969 +SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 1970 +SLAVE_CHANNEL_SQL_SKIP_COUNTER = 1971 +WRONG_FIELD_WITH_GROUP_V2 = 1972 +MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 1973 +ERROR_LAST = 1973 From 4d5848ea9ad46e41dab595525713d64d7df51f74 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 12 Sep 2016 07:49:51 +0900 Subject: [PATCH 041/396] Add test for Python 3.6 (#116) --- .travis.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f479e52..7a324e61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,15 @@ python: "3.5" cache: pip install: - - pip install tox + - pip install tox + +matrix: + include: + - python: "nightly" + env: + - TOX_ENV=py36 + - TESTDB=travis.cnf + env: matrix: @@ -17,11 +25,11 @@ env: global: - TESTDB=travis.cnf before_script: - - "mysql --help" - - "mysql --print-defaults" - - "mysql -e 'create database mysqldb_test charset utf8mb4;'" + - "mysql --help" + - "mysql --print-defaults" + - "mysql -e 'create database mysqldb_test charset utf8mb4;'" script: tox -e $TOX_ENV -# vim: sw=4 ts=4 sts=4 +# vim: sw=2 ts=2 sts=2 From 4202f611df07ae1274e7f0d71677cfd4cd778084 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 12 Sep 2016 07:50:05 +0900 Subject: [PATCH 042/396] Don't use mysql_real_escape_string_quote on MariaDB (#115) --- _mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_mysql.c b/_mysql.c index dea4d547..9e73f626 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1064,7 +1064,7 @@ _mysql_escape_string( if (self && PyModule_Check((PyObject*)self)) self = NULL; if (self && self->open) { -#if MYSQL_VERSION_ID >= 50707 +#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); #else len = mysql_real_escape_string(&(self->connection), out, in, size); @@ -1122,7 +1122,7 @@ _mysql_string_literal( out = PyBytes_AS_STRING(str); check_server_init(NULL); if (self && self->open) { -#if MYSQL_VERSION_ID >= 50707 +#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); #else len = mysql_real_escape_string(&(self->connection), out+1, in, size); From fe944673122b0dc651ffccca468cfcee1087f70c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 29 Sep 2016 17:20:58 +0900 Subject: [PATCH 043/396] Add basic Makefile --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..491cc75e --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: build +build: + python3 setup.py build_ext -if + + +.PHONY: clean +clean: + find . -name '*.pyc' -delete + find . -name '__pycache__' -delete + rm *.so + python3 setup.py clean From 7a402995c0a0d913ba0c8ffd05ecf12a9c344c07 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 29 Sep 2016 18:45:44 +0900 Subject: [PATCH 044/396] Update HISTORY --- HISTORY | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/HISTORY b/HISTORY index 97ffe20d..cc05a0fd 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,27 @@ +===================== + What's new in 1.3.8 +===================== + +Update error constants (#113) + +Use _binary prefix for bytes/bytearray parameters (#106) + +Use mysql_real_escape_string_quote() if exists (#109) + +Better Warning propergation (#101) + +Fix conversion error when mysql_affected_rows returns -1 + +Fix Cursor.callproc may raise TypeError (#90, #91) + +connect() support 'database' and 'password' keyword argument. + +Fix accessing to dungling pointer when using ssl (#78) + +Accept %% in Cursor.executemany (#83) + +Fix warning cause TypeError on Python 3 (#68) + ===================== What's new in 1.3.7 ===================== From d8b4d34874401fc261aa665a1e00a9e40dd915c8 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 29 Sep 2016 20:33:13 +0900 Subject: [PATCH 045/396] 1.3.8 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 3f6d5d1b..81bb4122 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.7 -version_info: (1,3,7,'final',1) +version: 1.3.8 +version_info: (1,3,8,'final',1) description: Python interface to MySQL long_description: ========================= From 387c3501886cc4fae7590d798393f3d8e8176916 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 30 Sep 2016 15:50:10 +0900 Subject: [PATCH 046/396] Fix compile error on MSVC compiler. fixes #121 --- _mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 9e73f626..6917f4e7 100644 --- a/_mysql.c +++ b/_mysql.c @@ -521,7 +521,7 @@ _mysql_ConnectionObject_Initialize( #if HAVE_OPENSSL char *key = NULL, *cert = NULL, *ca = NULL, *capath = NULL, *cipher = NULL; - PyObject *ssl_keepref[5] = {}; + PyObject *ssl_keepref[5] = {NULL}; int n_ssl_keepref = 0; #endif char *host = NULL, *user = NULL, *passwd = NULL, From 490080ac79800954c77dcae1711eaaf7d40c972f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 30 Sep 2016 21:13:41 +0900 Subject: [PATCH 047/396] Revert "Use _binary prefix for bytes/bytearray parameters" --- MySQLdb/__init__.py | 9 ++------- MySQLdb/connections.py | 11 ++--------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 9dc8dabc..fc414810 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -27,7 +27,6 @@ paramstyle = "format" from _mysql import * -from MySQLdb.compat import PY2 from MySQLdb.constants import FIELD_TYPE from MySQLdb.times import Date, Time, Timestamp, \ DateFromTicks, TimeFromTicks, TimestampFromTicks @@ -73,12 +72,8 @@ def test_DBAPISet_set_equality_membership(): def test_DBAPISet_set_inequality_membership(): assert FIELD_TYPE.DATE != STRING -if PY2: - def Binary(x): - return bytearray(x) -else: - def Binary(x): - return bytes(x) +def Binary(x): + return bytes(x) def Connect(*args, **kwargs): """Factory function for connections.Connection.""" diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index e8f6cc48..d8406db8 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -197,7 +197,7 @@ class object, used to create cursors (keyword only) db = proxy(self) def _get_string_literal(): - # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) + # Note: string_literal() is called for bytes object on Python 3. def string_literal(obj, dummy=None): return db.string_literal(obj) return string_literal @@ -213,11 +213,6 @@ def unicode_literal(u, dummy=None): return db.literal(str(u).encode(unicode_literal.charset)) return unicode_literal - def _get_bytes_literal(): - def bytes_literal(obj, dummy=None): - return b'_binary' + db.string_literal(obj) - return bytes_literal - def _get_string_decoder(): def string_decoder(s): return s.decode(string_decoder.charset) @@ -225,7 +220,6 @@ def string_decoder(s): string_literal = _get_string_literal() self.unicode_literal = unicode_literal = _get_unicode_literal() - bytes_literal = _get_bytes_literal() self.string_decoder = string_decoder = _get_string_decoder() if not charset: charset = self.character_set_name() @@ -240,8 +234,7 @@ def string_decoder(s): self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) - self.encoders[bytes] = string_literal if PY2 else bytes_literal - self.encoders[bytearray] = bytes_literal + self.encoders[bytes] = string_literal self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: From d2ee516e2ca526bf333d3c982c95c4c3e5b38514 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 30 Sep 2016 21:33:20 +0900 Subject: [PATCH 048/396] Update HISTORY --- HISTORY | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/HISTORY b/HISTORY index cc05a0fd..98227dcd 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,12 @@ +===================== + What's new in 1.3.9 +===================== + +Revert adding _binary prefix for bytes/bytearray parameter. It broke backward compatibility. + +Fix Windows compile error on MSVC. + + ===================== What's new in 1.3.8 ===================== From 74f7770b715fac0b22152cc5d5f2d09e70df3bef Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 30 Sep 2016 21:33:46 +0900 Subject: [PATCH 049/396] 1.3.9 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 81bb4122..322ba993 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.8 -version_info: (1,3,8,'final',1) +version: 1.3.9 +version_info: (1,3,9,'final',1) description: Python interface to MySQL long_description: ========================= From 42b68a43f7e8454047b2fdb627a43d00b428bd47 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 11 Oct 2016 16:31:29 +0900 Subject: [PATCH 050/396] Use Python 3.6-dev --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7a324e61..cb7f09d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ install: matrix: include: - - python: "nightly" + - python: "3.6-dev" env: - TOX_ENV=py36 - TESTDB=travis.cnf From 4050c747e540cc8029e95bf416ec1cc697337b19 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Wed, 12 Oct 2016 11:08:57 +0100 Subject: [PATCH 051/396] Fix some typos in HISTORY (#129) --- HISTORY | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HISTORY b/HISTORY index 98227dcd..a420fd91 100644 --- a/HISTORY +++ b/HISTORY @@ -17,19 +17,19 @@ Use _binary prefix for bytes/bytearray parameters (#106) Use mysql_real_escape_string_quote() if exists (#109) -Better Warning propergation (#101) +Better Warning propagation (#101) Fix conversion error when mysql_affected_rows returns -1 Fix Cursor.callproc may raise TypeError (#90, #91) -connect() support 'database' and 'password' keyword argument. +connect() supports the 'database' and 'password' keyword arguments. -Fix accessing to dungling pointer when using ssl (#78) +Fix accessing dangling pointer when using ssl (#78) Accept %% in Cursor.executemany (#83) -Fix warning cause TypeError on Python 3 (#68) +Fix warning that caused TypeError on Python 3 (#68) ===================== What's new in 1.3.7 From d16ced645f345017cbdfe789a4a69a6d3420869c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 4 Jan 2017 21:34:02 +0900 Subject: [PATCH 052/396] Add test for PyMySQL/mysqlclient-python#137 --- tests/test__mysql.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/test__mysql.py diff --git a/tests/test__mysql.py b/tests/test__mysql.py new file mode 100644 index 00000000..e2cb6088 --- /dev/null +++ b/tests/test__mysql.py @@ -0,0 +1,7 @@ +import pytest +import _mysql + + +def test_result_type(): + with pytest.raises(TypeError): + _mysql.result(b"xyz") From 10c6f9f767b5dca2b919285233ee9c12e84fea0b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 4 Jan 2017 21:37:24 +0900 Subject: [PATCH 053/396] Add typecheck for _mysql.result() fixes #137 --- _mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_mysql.c b/_mysql.c index 6917f4e7..e349e779 100644 --- a/_mysql.c +++ b/_mysql.c @@ -378,8 +378,8 @@ _mysql_ResultObject_Initialize( int n, i; MYSQL_FIELD *fields; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|iO", kwlist, - &conn, &use, &conv)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|iO", kwlist, + &_mysql_ConnectionObject_Type, &conn, &use, &conv)) return -1; if (!conv) { if (!(conv = PyDict_New())) From 2feb5ed6850a3905edf0333e0cd11ea6218f0f4f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 4 Jan 2017 21:43:42 +0900 Subject: [PATCH 054/396] reduce IS_PY3K usage --- _mysql.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/_mysql.c b/_mysql.c index e349e779..c3ebc16e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -51,11 +51,7 @@ PERFORMANCE OF THIS SOFTWARE. #include "errmsg.h" #define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) -#ifdef IS_PY3K -# define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) -#else -# define MyFree(ob) ob->ob_type->tp_free((PyObject *)ob) -#endif +#define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) static PyObject *_mysql_MySQLError; static PyObject *_mysql_Warning; @@ -2643,12 +2639,7 @@ _mysql_ResultObject_setattro( } PyTypeObject _mysql_ConnectionObject_Type = { -#ifdef IS_PY3K PyVarObject_HEAD_INIT(NULL, 0) -#else - PyObject_HEAD_INIT(NULL) - 0, -#endif "_mysql.connection", /* (char *)tp_name For printing */ sizeof(_mysql_ConnectionObject), 0, @@ -2715,12 +2706,7 @@ PyTypeObject _mysql_ConnectionObject_Type = { } ; PyTypeObject _mysql_ResultObject_Type = { -#ifdef IS_PY3K PyVarObject_HEAD_INIT(NULL, 0) -#else - PyObject_HEAD_INIT(NULL) - 0, -#endif "_mysql.result", sizeof(_mysql_ResultObject), 0, From 50a81b17835e66c7fb573a177fe0dcfa46eba851 Mon Sep 17 00:00:00 2001 From: Ardi Nusawan Date: Thu, 26 Jan 2017 15:37:19 +0700 Subject: [PATCH 055/396] adding requirement for Mac OS (#143) using homebrew --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 429770f8..60ec4ea5 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ On Windows, there are binary wheel you can install without MySQLConnector/C or M `sudo yum install python3-devel ` # Red Hat / CentOS +`brew install mysql-connector-c` # macOS (Homebrew) ### Install from PyPI From cba486e0438c2bbad9802667c7a1db0efc591b04 Mon Sep 17 00:00:00 2001 From: Vilnis Termanis Date: Fri, 10 Feb 2017 11:36:41 +0000 Subject: [PATCH 056/396] Use _binary prefix for bytes/bytearray parameters (#140) - Based on #106 but now disabled by default - Can be enabled via 'binary_prefix' connection parameter - Added unit tests to verify behaviour --- MySQLdb/__init__.py | 9 +++++++-- MySQLdb/connections.py | 24 ++++++++++++++++++++---- tests/test_MySQLdb_capabilities.py | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index fc414810..9dc8dabc 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -27,6 +27,7 @@ paramstyle = "format" from _mysql import * +from MySQLdb.compat import PY2 from MySQLdb.constants import FIELD_TYPE from MySQLdb.times import Date, Time, Timestamp, \ DateFromTicks, TimeFromTicks, TimestampFromTicks @@ -72,8 +73,12 @@ def test_DBAPISet_set_equality_membership(): def test_DBAPISet_set_inequality_membership(): assert FIELD_TYPE.DATE != STRING -def Binary(x): - return bytes(x) +if PY2: + def Binary(x): + return bytearray(x) +else: + def Binary(x): + return bytes(x) def Connect(*args, **kwargs): """Factory function for connections.Connection.""" diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index d8406db8..8375041c 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -137,6 +137,10 @@ class object, used to create cursors (keyword only) If True, autocommit is enabled. If None, autocommit isn't set and server default is used. + :param bool binary_prefix: + If set, the '_binary' prefix will be used for raw byte query + arguments (e.g. Binary). This is disabled by default. + There are a number of undocumented, non-standard methods. See the documentation for the MySQL C API for some hints on what they do. """ @@ -174,6 +178,7 @@ class object, used to create cursors (keyword only) use_unicode = kwargs2.pop('use_unicode', use_unicode) sql_mode = kwargs2.pop('sql_mode', '') + binary_prefix = kwargs2.pop('binary_prefix', False) client_flag = kwargs.get('client_flag', 0) client_version = tuple([ numeric_part(n) for n in _mysql.get_client_info().split('.')[:2] ]) @@ -197,7 +202,7 @@ class object, used to create cursors (keyword only) db = proxy(self) def _get_string_literal(): - # Note: string_literal() is called for bytes object on Python 3. + # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) def string_literal(obj, dummy=None): return db.string_literal(obj) return string_literal @@ -206,13 +211,18 @@ def _get_unicode_literal(): if PY2: # unicode_literal is called for only unicode object. def unicode_literal(u, dummy=None): - return db.literal(u.encode(unicode_literal.charset)) + return db.string_literal(u.encode(unicode_literal.charset)) else: # unicode_literal() is called for arbitrary object. def unicode_literal(u, dummy=None): - return db.literal(str(u).encode(unicode_literal.charset)) + return db.string_literal(str(u).encode(unicode_literal.charset)) return unicode_literal + def _get_bytes_literal(): + def bytes_literal(obj, dummy=None): + return b'_binary' + db.string_literal(obj) + return bytes_literal + def _get_string_decoder(): def string_decoder(s): return s.decode(string_decoder.charset) @@ -220,6 +230,7 @@ def string_decoder(s): string_literal = _get_string_literal() self.unicode_literal = unicode_literal = _get_unicode_literal() + bytes_literal = _get_bytes_literal() self.string_decoder = string_decoder = _get_string_decoder() if not charset: charset = self.character_set_name() @@ -234,7 +245,12 @@ def string_decoder(s): self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) - self.encoders[bytes] = string_literal + if binary_prefix: + self.encoders[bytes] = string_literal if PY2 else bytes_literal + self.encoders[bytearray] = bytes_literal + else: + self.encoders[bytes] = string_literal + self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 6f1fe27d..f1887575 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -2,10 +2,12 @@ # -*- coding: utf-8 -*- import capabilities from datetime import timedelta +from contextlib import closing import unittest import MySQLdb from MySQLdb.compat import unicode from MySQLdb import cursors +from configdb import connection_factory import warnings @@ -180,6 +182,23 @@ def test_warning_propagation(self): finally: c.close() + def test_binary_prefix(self): + # verify prefix behaviour when enabled, disabled and for default (disabled) + for binary_prefix in (True, False, None): + kwargs = self.connect_kwargs.copy() + # needs to be set to can guarantee CHARSET response for normal strings + kwargs['charset'] = 'utf8' + if binary_prefix != None: + kwargs['binary_prefix'] = binary_prefix + + with closing(connection_factory(**kwargs)) as conn: + with closing(conn.cursor()) as c: + c.execute('SELECT CHARSET(%s)', (MySQLdb.Binary(b'raw bytes'),)) + self.assertEqual(c.fetchall()[0][0], 'binary' if binary_prefix else 'utf8') + # normal strings should not get prefix + c.execute('SELECT CHARSET(%s)', ('str',)) + self.assertEqual(c.fetchall()[0][0], 'utf8') + if __name__ == '__main__': if test_MySQLdb.leak_test: From 97e18a1ea19180249fc38a50c556060fb437bf0f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 19 Feb 2017 20:11:49 +0900 Subject: [PATCH 057/396] stop using tox. --- .travis.yml | 34 ++++++++++++++++------------------ tox.ini | 12 ------------ 2 files changed, 16 insertions(+), 30 deletions(-) delete mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index cb7f09d5..6600eeeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,35 +1,33 @@ sudo: false +dist: trusty language: python -python: "3.5" +python: + - "nightly" + - "pypy-5.3.1" + - "3.6-dev" + - "3.6" + - "3.5" + - "3.4" + - "2.7" cache: pip -install: - - pip install tox - -matrix: - include: - - python: "3.6-dev" - env: - - TOX_ENV=py36 - - TESTDB=travis.cnf +install: + - pip install -U pip + - pip install -U mock coverage pytest pytest-cov env: - matrix: - - TOX_ENV=py26 - - TOX_ENV=py27 - - TOX_ENV=pypy - - TOX_ENV=py33 - - TOX_ENV=py34 - - TOX_ENV=py35 global: - TESTDB=travis.cnf + before_script: - "mysql --help" - "mysql --print-defaults" - "mysql -e 'create database mysqldb_test charset utf8mb4;'" -script: tox -e $TOX_ENV +script: + - pip install -e . + - py.test --cov ./MySQLdb # vim: sw=2 ts=2 sts=2 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 4c7fe5fd..00000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -[tox] -envlist = py26, py27, pypy, py33, py34, py35 - -[testenv] -passenv = TESTDB -commands = - py.test --cov {envsitepackagesdir}/MySQLdb -deps = - mock - coverage==3.7.1 - pytest - pytest-cov From cbc991ec2c08489ce7f58bc40fb98ace6f1f6d22 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 19 Feb 2017 20:40:16 +0900 Subject: [PATCH 058/396] travis: fix erros * set mysql password * add mysql to services --- .travis.yml | 3 +++ tests/travis.cnf | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6600eeeb..daeab25b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ python: cache: pip +services: + - mysql + install: - pip install -U pip - pip install -U mock coverage pytest pytest-cov diff --git a/tests/travis.cnf b/tests/travis.cnf index 483b76f2..b03dfa50 100644 --- a/tests/travis.cnf +++ b/tests/travis.cnf @@ -7,5 +7,5 @@ host = 127.0.0.1 port = 3306 user = root database = mysqldb_test -#password = +password = travis default-character-set = utf8mb4 From 2c0485f2465e60ebcc69395eb7ef138e6eae979e Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 19 Feb 2017 20:47:03 +0900 Subject: [PATCH 059/396] travis: use sudo-enabled trusty for now Random fail happens on container based image. --- .travis.yml | 2 +- tests/travis.cnf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index daeab25b..84f39ccb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: false +sudo: required dist: trusty language: python python: diff --git a/tests/travis.cnf b/tests/travis.cnf index b03dfa50..05ff8039 100644 --- a/tests/travis.cnf +++ b/tests/travis.cnf @@ -7,5 +7,5 @@ host = 127.0.0.1 port = 3306 user = root database = mysqldb_test -password = travis +#password = travis default-character-set = utf8mb4 From d587e81a7f09e1a0baf66ba3a16e7a7c0a05455c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 19 Feb 2017 20:59:39 +0900 Subject: [PATCH 060/396] codecov --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 84f39ccb..575f7e40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ services: install: - pip install -U pip - - pip install -U mock coverage pytest pytest-cov + - pip install -U mock coverage pytest pytest-cov codecov env: global: @@ -32,5 +32,8 @@ script: - pip install -e . - py.test --cov ./MySQLdb +after_succes: + - codecov + # vim: sw=2 ts=2 sts=2 From 118861f8cbe0463ea33f8b63cdf9d6ff41210e51 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 20 Feb 2017 16:47:56 +0900 Subject: [PATCH 061/396] deprecate Connection.__enter__ --- MySQLdb/connections.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 8375041c..d2a854f4 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -265,12 +265,10 @@ def autocommit(self, on): def cursor(self, cursorclass=None): """ - Create a cursor on which queries may be performed. The optional cursorclass parameter is used to create the Cursor. By default, self.cursorclass=cursors.Cursor is used. - """ return (cursorclass or self.cursorclass)(self) @@ -286,6 +284,9 @@ def query(self, query): _mysql.connection.query(self, query) def __enter__(self): + from warnings import warn + warn("context interface will be changed. Use explicit conn.commit() or conn.rollback().", + DeprecationWarning, 2) if self.get_autocommit(): self.query("BEGIN") return self.cursor() @@ -319,7 +320,7 @@ def begin(self): DEPRECATED: Will be removed in 1.3. Use an SQL BEGIN statement instead.""" from warnings import warn - warn("begin() is non-standard and will be removed in 1.3", + warn("begin() is non-standard and will be removed in 1.4", DeprecationWarning, 2) self.query("BEGIN") @@ -386,3 +387,5 @@ def show_warnings(self): NotSupportedError = NotSupportedError errorhandler = defaulterrorhandler + +# vim: colorcolumn=100 From 8fcd2c5b7d743db4e8c08a9f6e635e615d2cd08d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 20 Feb 2017 17:04:48 +0900 Subject: [PATCH 062/396] Use surrogateescape normally on Python 3.6 --- MySQLdb/connections.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index d2a854f4..1511024f 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -4,21 +4,29 @@ want to make your own subclasses. In most cases, you will probably override Connection.default_cursor with a non-standard Cursor class. """ +import re +import sys + from MySQLdb import cursors from MySQLdb.compat import unicode, PY2 -from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \ - DatabaseError, OperationalError, IntegrityError, InternalError, \ - NotSupportedError, ProgrammingError +from _mysql_exceptions import ( + Warning, Error, InterfaceError, DataError, + DatabaseError, OperationalError, IntegrityError, InternalError, + NotSupportedError, ProgrammingError, +) import _mysql -import re if not PY2: - # See http://bugs.python.org/issue24870 - _surrogateescape_table = [chr(i) if i < 0x80 else chr(i + 0xdc00) for i in range(256)] + if sys.version_info[:2] < (3, 6): + # See http://bugs.python.org/issue24870 + _surrogateescape_table = [chr(i) if i < 0x80 else chr(i + 0xdc00) for i in range(256)] - def _fast_surroundescape(s): - return s.decode('latin1').translate(_surrogateescape_table) + def _fast_surrogateescape(s): + return s.decode('latin1').translate(_surrogateescape_table) + else: + def _fast_surrogateescape(s): + return s.decode('ascii', 'surrogateescape') def defaulterrorhandler(connection, cursor, errorclass, errorvalue): @@ -312,7 +320,7 @@ def literal(self, o): # bytes to unicode and back again. # See http://python.org/dev/peps/pep-0383/ if not PY2 and isinstance(s, (bytes, bytearray)): - return _fast_surroundescape(s) + return _fast_surrogateescape(s) return s def begin(self): From 682dfa2b353323c77366bb8d0f1c90ca24e66e7e Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 20 Feb 2017 17:18:59 +0900 Subject: [PATCH 063/396] Add HISTORY for Python 1.3.10 --- HISTORY | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/HISTORY b/HISTORY index a420fd91..861e28e2 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,17 @@ +====================== + What's new in 1.3.10 +====================== + +Added `binary_prefix` option (disabled by default) to support +`_binary` prefix again. (#134) + +Fix SEGV of `_mysql.result()` when argument's type is unexpected. (#138) + +Deprecate context interface of Connection object. (#149) + +Don't use workaround of `bytes.decode('ascii', 'surrogateescape')` on Python 3.6+. (#150) + + ===================== What's new in 1.3.9 ===================== From c56146d137b2c9d55f9d15a5e3b502dd5a8457a1 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 20 Feb 2017 17:20:34 +0900 Subject: [PATCH 064/396] Add suffix to HISTORY and INSTALL --- HISTORY => HISTORY.md | 0 INSTALL => INSTALL.rst | 0 MANIFEST.in | 26 +++++++++++++------------- 3 files changed, 13 insertions(+), 13 deletions(-) rename HISTORY => HISTORY.md (100%) rename INSTALL => INSTALL.rst (100%) diff --git a/HISTORY b/HISTORY.md similarity index 100% rename from HISTORY rename to HISTORY.md diff --git a/INSTALL b/INSTALL.rst similarity index 100% rename from INSTALL rename to INSTALL.rst diff --git a/MANIFEST.in b/MANIFEST.in index 4e03cc43..b6bc8b68 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,13 +1,13 @@ -recursive-include doc *.rst -recursive-include tests *.py -include doc/conf.py -include MANIFEST.in -include HISTORY -include INSTALL -include README.md -include GPL-2.0 -include metadata.cfg -include site.cfg -include setup_common.py -include setup_posix.py -include setup_windows.py +recursive-include doc *.rst +recursive-include tests *.py +include doc/conf.py +include MANIFEST.in +include HISTORY.md +include INSTALL.rst +include README.md +include GPL-2.0 +include metadata.cfg +include site.cfg +include setup_common.py +include setup_posix.py +include setup_windows.py From 31337c4b63976d28299e1602664afa984cb1b473 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 20 Feb 2017 17:44:12 +0900 Subject: [PATCH 065/396] nose is not used now --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index fea65ab0..eb5b5513 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[test] -test_suite = nose.collector - [build_ext] ## Only uncomment/set these if the default configuration doesn't work ## Also see https://docs.python.org/distutils/configfile.html From 948def48bdba6d7f200648d68968afd60532470f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 20 Feb 2017 17:49:47 +0900 Subject: [PATCH 066/396] 1.3.10 --- metadata.cfg | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 322ba993..b40c91e4 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,14 +1,14 @@ [metadata] -version: 1.3.9 -version_info: (1,3,9,'final',1) +version: 1.3.10 +version_info: (1,3,10,'final',0) description: Python interface to MySQL long_description: ========================= Python interface to MySQL ========================= \n - mysqlclient is a fork of MySQL-python. It adds Python 3.3~ support - and merges some pull requests. + mysqlclient is a fork of MySQL-python. It adds Python 3 support + and fixed many bugs. \n MySQLdb is an interface to the popular MySQL_ database server for Python. The design goals are: @@ -17,8 +17,8 @@ long_description: - Thread-safety - Thread-friendliness (threads will not block each other) \n - MySQL-4.1 through 5.5 and Python-2.7, 3.3-3.5 are currently - supported. PyPy is supported. + MySQL-5.1 through 5.7 and Python 2.7, 3.3+ are currently + supported. PyPy is supported too. \n MySQLdb is `Free Software`_. \n @@ -50,20 +50,20 @@ classifiers: Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: _mysql_exceptions MySQLdb.compat - MySQLdb.converters MySQLdb.connections + MySQLdb.converters MySQLdb.cursors MySQLdb.release MySQLdb.times + MySQLdb.constants.CLIENT MySQLdb.constants.CR - MySQLdb.constants.FIELD_TYPE MySQLdb.constants.ER + MySQLdb.constants.FIELD_TYPE MySQLdb.constants.FLAG MySQLdb.constants.REFRESH - MySQLdb.constants.CLIENT - From 2b6123e236bb9112d511e57f40da4133cac4121f Mon Sep 17 00:00:00 2001 From: Guangyang Li Date: Tue, 28 Feb 2017 14:27:16 -0500 Subject: [PATCH 067/396] Allow semicolon at the end of bulk insert query --- MySQLdb/cursors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 9ebf0a14..d5f38285 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -28,7 +28,7 @@ RE_INSERT_VALUES = re.compile( r"\s*((?:INSERT|REPLACE)\s.+\sVALUES?\s+)" + r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" + - r"(\s*(?:ON DUPLICATE.*)?)\Z", + r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z", re.IGNORECASE | re.DOTALL) From bb07f701f5cdb35ebbf36085bfaecd2f7a71e7dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dor=C5=88=C3=A1k?= Date: Tue, 7 Mar 2017 10:48:28 +0100 Subject: [PATCH 068/396] include LICENSE in source tarball GPL-2.0 was removed in b6601fef9efff4786671bb2ecb99ee40d716d1aa --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index b6bc8b68..4f853e82 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,7 +5,7 @@ include MANIFEST.in include HISTORY.md include INSTALL.rst include README.md -include GPL-2.0 +include LICENSE include metadata.cfg include site.cfg include setup_common.py From f96cb44ef18007a33d1400ff98ce9b887cd983d3 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Sat, 29 Apr 2017 10:24:09 -0700 Subject: [PATCH 069/396] Add NEWDECIMAL to the NUMBER DBAPISet (#167) --- MySQLdb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 9dc8dabc..038309af 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -54,7 +54,7 @@ def __eq__(self, other): FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB]) NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, - FIELD_TYPE.TINY, FIELD_TYPE.YEAR]) + FIELD_TYPE.TINY, FIELD_TYPE.YEAR, FIELD_TYPE.NEWDECIMAL]) DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE]) TIME = DBAPISet([FIELD_TYPE.TIME]) TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) From 1693848c9f6ca863868d94d63499830f7f4f3a1f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 7 Jun 2017 15:46:20 +0900 Subject: [PATCH 070/396] Fix compile error with MariaDB 10.2. (#177) Don't touch MYSQL.reconnect directly. --- _mysql.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index c3ebc16e..8295f6ce 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1908,7 +1908,10 @@ _mysql_ConnectionObject_ping( int r, reconnect = -1; if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; check_connection(self); - if ( reconnect != -1 ) self->connection.reconnect = reconnect; + if (reconnect != -1) { + my_bool recon = reconnect; + mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); + } Py_BEGIN_ALLOW_THREADS r = mysql_ping(&(self->connection)); Py_END_ALLOW_THREADS From 354dcb59e286b0db3b108c92760d3ec135a8bc23 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 15 Jun 2017 09:06:46 +0900 Subject: [PATCH 071/396] Allow bulk insert which no space around `VALUES` (#179) --- MySQLdb/cursors.py | 2 +- tests/test_cursor.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index d5f38285..dfbd736b 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -26,7 +26,7 @@ #: executemany only supports simple bulk insert. #: You can use it to load large dataset. RE_INSERT_VALUES = re.compile( - r"\s*((?:INSERT|REPLACE)\s.+\sVALUES?\s+)" + + r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)" + r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" + r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z", re.IGNORECASE | re.DOTALL) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index bfdcb33a..535afec3 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -49,6 +49,10 @@ def test_executemany(): assert m is not None, 'error parse %(id_name)s' assert m.group(3) == ' ON duplicate update', 'group 3 not ON duplicate update, bug in RE_INSERT_VALUES?' + # https://github.com/PyMySQL/mysqlclient-python/issues/178 + m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO bloup(foo, bar)VALUES(%s, %s)") + assert m is not None + # cursor._executed myst bee "insert into test (data) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)" # list args data = range(10) From f41e651e280a625c0a782789eefd478fb112dea8 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 16 Jun 2017 16:58:59 +0900 Subject: [PATCH 072/396] fix leak of connection->converter. (#182) --- _mysql.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/_mysql.c b/_mysql.c index 8295f6ce..6c5e07ec 100644 --- a/_mysql.c +++ b/_mysql.c @@ -495,13 +495,10 @@ static int _mysql_ResultObject_traverse( return 0; } -static int _mysql_ResultObject_clear( - _mysql_ResultObject *self) +static int _mysql_ResultObject_clear(_mysql_ResultObject *self) { - Py_XDECREF(self->converter); - self->converter = NULL; - Py_XDECREF(self->conn); - self->conn = NULL; + Py_CLEAR(self->converter); + Py_CLEAR(self->conn); return 0; } @@ -796,8 +793,7 @@ _mysql_ConnectionObject_close( return NULL; } _mysql_ConnectionObject_clear(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_affected_rows__doc__ [] = @@ -2162,6 +2158,7 @@ _mysql_ConnectionObject_dealloc( mysql_close(&(self->connection)); self->open = 0; } + Py_CLEAR(self->converter); MyFree(self); } From 1a10d0e4fc7b95389fa2a7eb68171040135191a6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 27 Jun 2017 12:10:11 +0900 Subject: [PATCH 073/396] Support error numbers > CR_MAX_ERROR (#188) fixes #187 --- _mysql.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/_mysql.c b/_mysql.c index 6c5e07ec..8a515511 100644 --- a/_mysql.c +++ b/_mysql.c @@ -119,16 +119,7 @@ _mysql_Exception(_mysql_ConnectionObject *c) return NULL; } merr = mysql_errno(&(c->connection)); - if (!merr) - e = _mysql_InterfaceError; - else if (merr > CR_MAX_ERROR) { - PyTuple_SET_ITEM(t, 0, PyInt_FromLong(-1L)); - PyTuple_SET_ITEM(t, 1, PyString_FromString("error totally whack")); - PyErr_SetObject(_mysql_InterfaceError, t); - Py_DECREF(t); - return NULL; - } - else switch (merr) { + switch (merr) { case CR_COMMANDS_OUT_OF_SYNC: case ER_DB_CREATE_EXISTS: case ER_SYNTAX_ERROR: From 32ae0403d6bf00dd6edf89cac12a2b0c26fe6186 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 6 Jul 2017 22:31:52 +0900 Subject: [PATCH 074/396] add ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..4b7cf394 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +**IF YOU HAVE SOME TROUBLE, IT'S MAY NOT ISSUE OF THIS PROJECT. GO STACKOVERFLOW!!!** + +If you failed to build, go Stackoverflow. +If you failed to install, go Stackoverflow. +If you failed to connect to database, go Stackoverflow. + +FYI, MySQL Connector/C 6.1.10 has bug. see https://github.com/PyMySQL/mysqlclient-python/issues/169#issuecomment-299778504 + +Only when If you're sure it's PyMySQL's issue, report the complete steps to reproduce, from creating database. + +I don't have time to investigate your issue from an incomplete code snippet. + +See also: https://medium.com/@methane/why-you-must-not-ask-questions-on-github-issues-51d741d83fde From ca41a647cbb00a8699cbe2bd08918fa6e6e29e39 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 28 Aug 2017 22:35:27 +0900 Subject: [PATCH 075/396] remove include "my_config.h". (#198) fixes #197 --- _mysql.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/_mysql.c b/_mysql.c index 8a515511..b0b8fdeb 100644 --- a/_mysql.c +++ b/_mysql.c @@ -26,17 +26,9 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "my_config.h" #include "mysql.h" #include "mysqld_error.h" -#ifdef HAVE_WCSCOLL -#undef HAVE_WCSCOLL -#endif -#ifdef SIZEOF_SIZE_T -#undef SIZEOF_SIZE_T -#endif - #include "Python.h" #if PY_MAJOR_VERSION >= 3 #define IS_PY3K From 92706a7dc72582b3367c365b4eef9bf46d2a847a Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 29 Aug 2017 16:33:55 +0900 Subject: [PATCH 076/396] 1.3.11 --- HISTORY.md | 21 ++++++++++++++++++--- metadata.cfg | 7 +++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 861e28e2..f2c32136 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,18 @@ +====================== + What's new in 1.3.11 +====================== + +Support MariaDB 10.2 client library (#197, #177) + +Add NEWDECIMAL to the NUMBER DBAPISet (#167) + +Allow bulk insert which no space around `VALUES` (#179) + +fix leak of connection->converter. (#182) + +Support error numbers > CR_MAX_ERROR (#188) + + ====================== What's new in 1.3.10 ====================== @@ -16,7 +31,7 @@ Don't use workaround of `bytes.decode('ascii', 'surrogateescape')` on Python 3.6 What's new in 1.3.9 ===================== -Revert adding _binary prefix for bytes/bytearray parameter. It broke backward compatibility. +Revert adding `_binary` prefix for bytes/bytearray parameter. It broke backward compatibility. Fix Windows compile error on MSVC. @@ -27,7 +42,7 @@ Fix Windows compile error on MSVC. Update error constants (#113) -Use _binary prefix for bytes/bytearray parameters (#106) +Use `_binary` prefix for bytes/bytearray parameters (#106) Use mysql_real_escape_string_quote() if exists (#109) @@ -149,7 +164,7 @@ beta 5 Another internal fix for handling remapped character sets. -_mysql.c was broken for the case where read_timeout was *not* available. (Issue #6) +`_mysql.c` was broken for the case where read_timeout was *not* available. (Issue #6) Documentation was converted to sphinx but there is a lot of cleanup left to do. diff --git a/metadata.cfg b/metadata.cfg index b40c91e4..8c57197b 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.10 -version_info: (1,3,10,'final',0) +version: 1.3.11 +version_info: (1,3,11,'final',0) description: Python interface to MySQL long_description: ========================= @@ -17,7 +17,7 @@ long_description: - Thread-safety - Thread-friendliness (threads will not block each other) \n - MySQL-5.1 through 5.7 and Python 2.7, 3.3+ are currently + MySQL-5.5 through 5.7 and Python 2.7, 3.4+ are currently supported. PyPy is supported too. \n MySQLdb is `Free Software`_. @@ -47,7 +47,6 @@ classifiers: Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 From e39df07f29735fb4fef3db14c6b2418184f5e3ba Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 29 Aug 2017 18:16:15 +0900 Subject: [PATCH 077/396] Fix docstring for use_unicode type --- MySQLdb/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 1511024f..8d9f2dd5 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -110,7 +110,7 @@ def __init__(self, *args, **kwargs): :param type cursorclass: class object, used to create cursors (keyword only) - :param str use_unicode: + :param bool use_unicode: If True, text-like columns are returned as unicode objects using the connection's character set. Otherwise, text-like columns are returned as strings. columns are returned as From ca4317a6b5ffc4c78c711123b52dfda400d00eb5 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 30 Aug 2017 18:49:02 +0900 Subject: [PATCH 078/396] Fix encoding tuple argument (#155) Since Connections.encoders is broken by design. Tuple and list is escaped directly in `Connection.literal()`. Removed tuple and list from converters mapping. Fixes #145 --- MySQLdb/connections.py | 82 +++++++++++++++++++++--------------------- MySQLdb/converters.py | 8 ++--- MySQLdb/cursors.py | 10 +++--- _mysql.c | 2 ++ 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 8d9f2dd5..e5d9fa6f 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -186,7 +186,7 @@ class object, used to create cursors (keyword only) use_unicode = kwargs2.pop('use_unicode', use_unicode) sql_mode = kwargs2.pop('sql_mode', '') - binary_prefix = kwargs2.pop('binary_prefix', False) + self._binary_prefix = kwargs2.pop('binary_prefix', False) client_flag = kwargs.get('client_flag', 0) client_version = tuple([ numeric_part(n) for n in _mysql.get_client_info().split('.')[:2] ]) @@ -208,38 +208,28 @@ class object, used to create cursors (keyword only) self._server_version = tuple([ numeric_part(n) for n in self.get_server_info().split('.')[:2] ]) + self.encoding = 'ascii' # overriden in set_character_set() db = proxy(self) - def _get_string_literal(): - # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) - def string_literal(obj, dummy=None): - return db.string_literal(obj) - return string_literal - - def _get_unicode_literal(): - if PY2: - # unicode_literal is called for only unicode object. - def unicode_literal(u, dummy=None): - return db.string_literal(u.encode(unicode_literal.charset)) - else: - # unicode_literal() is called for arbitrary object. - def unicode_literal(u, dummy=None): - return db.string_literal(str(u).encode(unicode_literal.charset)) - return unicode_literal - - def _get_bytes_literal(): - def bytes_literal(obj, dummy=None): - return b'_binary' + db.string_literal(obj) - return bytes_literal - - def _get_string_decoder(): - def string_decoder(s): - return s.decode(string_decoder.charset) - return string_decoder - - string_literal = _get_string_literal() - self.unicode_literal = unicode_literal = _get_unicode_literal() - bytes_literal = _get_bytes_literal() - self.string_decoder = string_decoder = _get_string_decoder() + + # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) + def string_literal(obj, dummy=None): + return db.string_literal(obj) + + if PY2: + # unicode_literal is called for only unicode object. + def unicode_literal(u, dummy=None): + return db.string_literal(u.encode(db.encoding)) + else: + # unicode_literal() is called for arbitrary object. + def unicode_literal(u, dummy=None): + return db.string_literal(str(u).encode(db.encoding)) + + def bytes_literal(obj, dummy=None): + return b'_binary' + db.string_literal(obj) + + def string_decoder(s): + return s.decode(db.encoding) + if not charset: charset = self.character_set_name() self.set_character_set(charset) @@ -253,12 +243,7 @@ def string_decoder(s): self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) - if binary_prefix: - self.encoders[bytes] = string_literal if PY2 else bytes_literal - self.encoders[bytearray] = bytes_literal - else: - self.encoders[bytes] = string_literal - + self.encoders[bytes] = string_literal self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: @@ -305,6 +290,16 @@ def __exit__(self, exc, value, tb): else: self.commit() + def _bytes_literal(self, bs): + assert isinstance(bs, (bytes, bytearray)) + x = self.string_literal(bs) # x is escaped and quoted bytes + if self._binary_prefix: + return b'_binary' + x + return x + + def _tuple_literal(self, t, d): + return "(%s)" % (','.join(map(self.literal, t))) + def literal(self, o): """If o is a single object, returns an SQL literal as a string. If o is a non-string sequence, the items of the sequence are @@ -313,7 +308,14 @@ def literal(self, o): Non-standard. For internal use; do not use this in your applications. """ - s = self.escape(o, self.encoders) + if isinstance(o, bytearray): + s = self._bytes_literal(o) + elif not PY2 and isinstance(o, bytes): + s = self._bytes_literal(o) + elif isinstance(o, (tuple, list)): + s = self._tuple_literal(o) + else: + s = self.escape(o, self.encoders) # Python 3(~3.4) doesn't support % operation for bytes object. # We should decode it before using %. # Decoding with ascii and surrogateescape allows convert arbitrary @@ -360,8 +362,6 @@ def set_character_set(self, charset): raise NotSupportedError("server is too old to set charset") self.query('SET NAMES %s' % charset) self.store_result() - self.string_decoder.charset = py_charset - self.unicode_literal.charset = py_charset self.encoding = py_charset def set_sql_mode(self, sql_mode): diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 8a9908d4..505a4df0 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -29,10 +29,9 @@ Don't modify conversions if you can avoid it. Instead, make copies (with the copy() method), modify the copies, and then pass them to MySQL.connect(). - """ -from _mysql import string_literal, escape_sequence, escape_dict, escape, NULL +from _mysql import string_literal, escape, NULL from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * from MySQLdb.compat import PY2, long @@ -53,6 +52,7 @@ def Str2Set(s): return set([ i for i in s.split(',') if i ]) def Set2Str(s, d): + # Only support ascii string. Not tested. return string_literal(','.join(s), d) def Thing2Str(s, d): @@ -97,9 +97,6 @@ def quote_tuple(t, d): long: Thing2Str, float: Float2Str, NoneType: None2NULL, - tuple: quote_tuple, - list: quote_tuple, - dict: escape_dict, ArrayType: array2Str, bool: Bool2Str, Date: Thing2Literal, @@ -107,6 +104,7 @@ def quote_tuple(t, d): DateTimeDeltaType: DateTimeDelta2literal, str: Thing2Literal, # default set: Set2Str, + FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, FIELD_TYPE.LONG: long, diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index dfbd736b..3769ab5e 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -225,7 +225,7 @@ def execute(self, query, args=None): # db.literal(obj) always returns str. if PY2 and isinstance(query, unicode): - query = query.encode(db.unicode_literal.charset) + query = query.encode(db.encoding) if args is not None: if isinstance(args, dict): @@ -233,14 +233,14 @@ def execute(self, query, args=None): else: args = tuple(map(db.literal, args)) if not PY2 and isinstance(query, (bytes, bytearray)): - query = query.decode(db.unicode_literal.charset) + query = query.decode(db.encoding) try: query = query % args except TypeError as m: self.errorhandler(self, ProgrammingError, str(m)) if isinstance(query, unicode): - query = query.encode(db.unicode_literal.charset, 'surrogateescape') + query = query.encode(db.encoding, 'surrogateescape') res = None try: @@ -353,7 +353,7 @@ def callproc(self, procname, args=()): q = "SET @_%s_%d=%s" % (procname, index, db.literal(arg)) if isinstance(q, unicode): - q = q.encode(db.unicode_literal.charset, 'surrogateescape') + q = q.encode(db.encoding, 'surrogateescape') self._query(q) self.nextset() @@ -361,7 +361,7 @@ def callproc(self, procname, args=()): ','.join(['@_%s_%d' % (procname, i) for i in range(len(args))])) if isinstance(q, unicode): - q = q.encode(db.unicode_literal.charset, 'surrogateescape') + q = q.encode(db.encoding, 'surrogateescape') self._query(q) self._executed = q if not self._defer_warnings: diff --git a/_mysql.c b/_mysql.c index b0b8fdeb..36672373 100644 --- a/_mysql.c +++ b/_mysql.c @@ -2777,12 +2777,14 @@ _mysql_methods[] = { _mysql_escape__doc__ }, { + // deprecated. "escape_sequence", (PyCFunction)_mysql_escape_sequence, METH_VARARGS, _mysql_escape_sequence__doc__ }, { + // deprecated. "escape_dict", (PyCFunction)_mysql_escape_dict, METH_VARARGS, From bfefd34bc17430046ddf50fb2a37ced810f62467 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 30 Aug 2017 18:49:49 +0900 Subject: [PATCH 079/396] update HISTORY --- HISTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index f2c32136..74e20f1e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,6 +12,8 @@ fix leak of connection->converter. (#182) Support error numbers > CR_MAX_ERROR (#188) +Fix tuple argument support (#145) + ====================== What's new in 1.3.10 From 823e56a4b295abc35f5b28b836c1750288b115d8 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 30 Aug 2017 18:51:07 +0900 Subject: [PATCH 080/396] 1.3.11c1 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 8c57197b..f53ba760 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.11 -version_info: (1,3,11,'final',0) +version: 1.3.11c1 +version_info: (1,3,11,'c',1) description: Python interface to MySQL long_description: ========================= From 6058715077d7b63599eefd6eb8460548cba7c951 Mon Sep 17 00:00:00 2001 From: Ramiro Aparicio Date: Thu, 31 Aug 2017 03:53:55 +0200 Subject: [PATCH 081/396] Fix compile with MariaDB 10.2.8 (#200) --- _mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_mysql.c b/_mysql.c index 36672373..2134057e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1039,7 +1039,7 @@ _mysql_escape_string( if (self && PyModule_Check((PyObject*)self)) self = NULL; if (self && self->open) { -#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) +#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); #else len = mysql_real_escape_string(&(self->connection), out, in, size); @@ -1097,7 +1097,7 @@ _mysql_string_literal( out = PyBytes_AS_STRING(str); check_server_init(NULL); if (self && self->open) { -#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) +#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); #else len = mysql_real_escape_string(&(self->connection), out+1, in, size); From 71e15b1b3be2c685eee27459f1c8ad9a4350e8fc Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 1 Sep 2017 10:54:36 +0900 Subject: [PATCH 082/396] 1.3.11 --- HISTORY.md | 6 +++--- metadata.cfg | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 74e20f1e..3f043ee3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,15 +2,15 @@ What's new in 1.3.11 ====================== -Support MariaDB 10.2 client library (#197, #177) +Support MariaDB 10.2 client library (#197, #177, #200) Add NEWDECIMAL to the NUMBER DBAPISet (#167) Allow bulk insert which no space around `VALUES` (#179) -fix leak of connection->converter. (#182) +Fix leak of `connection->converter`. (#182) -Support error numbers > CR_MAX_ERROR (#188) +Support error `numbers > CR_MAX_ERROR` (#188) Fix tuple argument support (#145) diff --git a/metadata.cfg b/metadata.cfg index f53ba760..94f11f83 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.11c1 -version_info: (1,3,11,'c',1) +version: 1.3.11 +version_info: (1,3,11,'final',0) description: Python interface to MySQL long_description: ========================= @@ -17,8 +17,8 @@ long_description: - Thread-safety - Thread-friendliness (threads will not block each other) \n - MySQL-5.5 through 5.7 and Python 2.7, 3.4+ are currently - supported. PyPy is supported too. + MySQL-5.5 through 5.7 and Python 2.7, 3.4+ are currently supported. + PyPy is supported too. \n MySQLdb is `Free Software`_. \n @@ -47,6 +47,7 @@ classifiers: Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 From bab17ed5049afc4247601f44ed3c52768c1b2fe4 Mon Sep 17 00:00:00 2001 From: NestorTejero Date: Fri, 1 Sep 2017 18:39:41 +0200 Subject: [PATCH 083/396] remove extra parameter (#201) This produced the error `TypeError: _tuple_literal() missing 1 required positional argument: 'd'` on line 316. --- MySQLdb/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index e5d9fa6f..c03e128d 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -297,7 +297,7 @@ def _bytes_literal(self, bs): return b'_binary' + x return x - def _tuple_literal(self, t, d): + def _tuple_literal(self, t): return "(%s)" % (','.join(map(self.literal, t))) def literal(self, o): From 1a9874b340a3d0dda1470509c924ad9fc8284df7 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 2 Sep 2017 01:50:16 +0900 Subject: [PATCH 084/396] raise InterfaceError when mysql_errno() returns 0 (#203) --- _mysql.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_mysql.c b/_mysql.c index 2134057e..49c8d9b1 100644 --- a/_mysql.c +++ b/_mysql.c @@ -112,6 +112,9 @@ _mysql_Exception(_mysql_ConnectionObject *c) } merr = mysql_errno(&(c->connection)); switch (merr) { + case 0: + e = _mysql_InterfaceError; + break; case CR_COMMANDS_OUT_OF_SYNC: case ER_DB_CREATE_EXISTS: case ER_SYNTAX_ERROR: From 46e55812386ab9aab169b6a5f8daa0619c50792d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 2 Sep 2017 02:39:45 +0900 Subject: [PATCH 085/396] Update HISTORY --- HISTORY.md => HISTORY.rst | 8 ++++++++ MANIFEST.in | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) rename HISTORY.md => HISTORY.rst (97%) diff --git a/HISTORY.md b/HISTORY.rst similarity index 97% rename from HISTORY.md rename to HISTORY.rst index 3f043ee3..b6aa8c2b 100644 --- a/HISTORY.md +++ b/HISTORY.rst @@ -1,3 +1,11 @@ +====================== + What's new in 1.3.12 +====================== + +Fix tuple argument again (#201) + +InterfaceError is raised when Connection.query() is called for closed connection (#202) + ====================== What's new in 1.3.11 ====================== diff --git a/MANIFEST.in b/MANIFEST.in index 4f853e82..415a995a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,7 +2,7 @@ recursive-include doc *.rst recursive-include tests *.py include doc/conf.py include MANIFEST.in -include HISTORY.md +include HISTORY.rst include INSTALL.rst include README.md include LICENSE From 2996f9bebc90b8bb1c51a3c12285e59e1fb2fcf0 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 2 Sep 2017 02:40:15 +0900 Subject: [PATCH 086/396] 1.3.12 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 94f11f83..cf40b262 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.11 -version_info: (1,3,11,'final',0) +version: 1.3.12 +version_info: (1,3,12,'final',0) description: Python interface to MySQL long_description: ========================= From 73ccdcdddf2dd8e193e8af4a2362727c601b40ac Mon Sep 17 00:00:00 2001 From: kilroy42 Date: Thu, 14 Dec 2017 09:30:29 +0100 Subject: [PATCH 087/396] Fix decoding tiny/medium/long blobs (#215) --- MySQLdb/connections.py | 7 +++---- tests/test_MySQLdb_capabilities.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index c03e128d..6d5f402e 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -238,10 +238,9 @@ def string_decoder(s): self.set_sql_mode(sql_mode) if use_unicode: - self.converter[FIELD_TYPE.STRING].append((None, string_decoder)) - self.converter[FIELD_TYPE.VAR_STRING].append((None, string_decoder)) - self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) - self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) + for t in (FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING, FIELD_TYPE.VARCHAR, FIELD_TYPE.TINY_BLOB, + FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB): + self.converter[t].append((None, string_decoder)) self.encoders[bytes] = string_literal self.encoders[unicode] = unicode_literal diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index f1887575..0427ed74 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -116,7 +116,7 @@ def test_MULTIPOLYGON(self): c.execute("SELECT id, AsText(border) FROM test_MULTIPOLYGON") row = c.fetchone() self.assertEqual(row[0], 1) - self.assertEqual(row[1], b'MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))') + self.assertEqual(row[1], 'MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))') c.execute("SELECT id, AsWKB(border) FROM test_MULTIPOLYGON") row = c.fetchone() From b948553229f6d25fb9a586185b8fec21b5bf4b30 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Thu, 21 Dec 2017 18:18:57 +0900 Subject: [PATCH 088/396] simplify memory management of the conv dictionary In ResultObject_Initialize, always borrow the conv dictionary from the caller. This simplifies the code, removes an allocation, and fixes ref leaks in error cases. --- _mysql.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/_mysql.c b/_mysql.c index 49c8d9b1..a3c53796 100644 --- a/_mysql.c +++ b/_mysql.c @@ -363,12 +363,6 @@ _mysql_ResultObject_Initialize( if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|iO", kwlist, &_mysql_ConnectionObject_Type, &conn, &use, &conv)) return -1; - if (!conv) { - if (!(conv = PyDict_New())) - return -1; - } - else - Py_INCREF(conv); self->conn = (PyObject *) conn; Py_INCREF(conn); @@ -387,13 +381,11 @@ _mysql_ResultObject_Initialize( return -1; } self->converter = PyTuple_New(0); - Py_DECREF(conv); return 0; } n = mysql_num_fields(result); self->nfields = n; if (!(self->converter = PyTuple_New(n))) { - Py_DECREF(conv); return -1; } fields = mysql_fetch_fields(result); @@ -401,15 +393,13 @@ _mysql_ResultObject_Initialize( PyObject *tmp, *fun; tmp = PyInt_FromLong((long) fields[i].type); if (!tmp) { - Py_DECREF(conv); return -1; } - fun = PyObject_GetItem(conv, tmp); + fun = conv ? PyObject_GetItem(conv, tmp) : NULL; Py_DECREF(tmp); if (!fun) { if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(PyExc_KeyError)) { - Py_DECREF(conv); return -1; } PyErr_Clear(); @@ -428,7 +418,6 @@ _mysql_ResultObject_Initialize( PyObject *t = PySequence_GetItem(fun, j); if (!t) { Py_DECREF(fun); - Py_DECREF(conv); return -1; } if (PyTuple_Check(t) && PyTuple_GET_SIZE(t) == 2) { @@ -463,7 +452,6 @@ _mysql_ResultObject_Initialize( PyTuple_SET_ITEM(self->converter, i, fun); } - Py_DECREF(conv); return 0; } From d65e10e356035f73584c59b381fbd0a01033dfd2 Mon Sep 17 00:00:00 2001 From: Graham Voysey Date: Thu, 21 Dec 2017 04:41:31 -0500 Subject: [PATCH 089/396] Add note about annoying MySQL Connector bug. (#212) --- README.md | 35 ++++++++++++++++++++++++++++++----- setup_posix.py | 2 ++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 60ec4ea5..2262e9d5 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ I hope this fork is merged back to MySQLdb1 like distribute was merged back to s You may need to install the Python and MySQL development headers and libraries like so: -`sudo apt-get install python-dev libmysqlclient-dev` # Debian / Ubuntu +* `sudo apt-get install python-dev libmysqlclient-dev` # Debian / Ubuntu +* `sudo yum install python-devel mysql-devel` # Red Hat / CentOS +* `brew install mysql-connector-c` # macOS (Homebrew) (Currently, it has bug. See below) -`sudo yum install python-devel mysql-devel` # Red Hat / CentOS - -On Windows, there are binary wheel you can install without MySQLConnector/C or MSVC. +On Windows, there are binary wheels you can install without MySQLConnector/C or MSVC. #### Note on Python 3 : if you are using python3 then you need to install python3-dev using the following command : @@ -25,7 +25,32 @@ On Windows, there are binary wheel you can install without MySQLConnector/C or M `sudo yum install python3-devel ` # Red Hat / CentOS -`brew install mysql-connector-c` # macOS (Homebrew) +#### **Note about bug of MySQL Connector/C on macOS** + +See also: https://bugs.mysql.com/bug.php?id=86971 + +Versions of MySQL Connector/C may have incorrect default configuration options that cause compilation errors when `mysqlclient-python` is installed. (As of November 2017, this is known to be true for homebrew's `mysql-connector-c` and [official package](https://dev.mysql.com/downloads/connector/c/)) + +Modification of `mysql_config` resolves these issues as follows. + +Change + +``` +# on macOS, on or about line 112: +# Create options +libs="-L$pkglibdir" +libs="$libs -l " +``` + +to + +``` +# Create options +libs="-L$pkglibdir" +libs="$libs -lmysqlclient -lssl -lcrypto" +``` + +An improper ssl configuration may also create issues; see, e.g, `brew info openssl` for details on macOS. ### Install from PyPI diff --git a/setup_posix.py b/setup_posix.py index 320e9e99..14cffcad 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -9,6 +9,8 @@ # of mysql_config def dequote(s): + if not s: + raise Exception("Wrong MySQL configuration: maybe https://bugs.mysql.com/bug.php?id=86971 ?") if s[0] in "\"'" and s[0] == s[-1]: s = s[1:-1] return s From b10ecd0b46b8acec72c618826b4a61137b42cf1a Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 21 Dec 2017 18:48:50 +0900 Subject: [PATCH 090/396] Remove broken row_seek() and row_tell() (#220) They are broken from long ago and making them safe is difficult. So remove them rather than fix. --- _mysql.c | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/_mysql.c b/_mysql.c index a3c53796..5216fa5b 100644 --- a/_mysql.c +++ b/_mysql.c @@ -2166,46 +2166,6 @@ _mysql_ResultObject_data_seek( return Py_None; } -static char _mysql_ResultObject_row_seek__doc__[] = -"row_seek(n) -- seek by offset n rows of result set"; -static PyObject * -_mysql_ResultObject_row_seek( - _mysql_ResultObject *self, - PyObject *args) -{ - int offset; - MYSQL_ROW_OFFSET r; - if (!PyArg_ParseTuple(args, "i:row_seek", &offset)) return NULL; - check_result_connection(self); - if (self->use) { - PyErr_SetString(_mysql_ProgrammingError, - "cannot be used with connection.use_result()"); - return NULL; - } - r = mysql_row_tell(self->result); - mysql_row_seek(self->result, r+offset); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ResultObject_row_tell__doc__[] = -"row_tell() -- return the current row number of the result set."; -static PyObject * -_mysql_ResultObject_row_tell( - _mysql_ResultObject *self, - PyObject *noargs) -{ - MYSQL_ROW_OFFSET r; - check_result_connection(self); - if (self->use) { - PyErr_SetString(_mysql_ProgrammingError, - "cannot be used with connection.use_result()"); - return NULL; - } - r = mysql_row_tell(self->result); - return PyInt_FromLong(r-self->result->data->data); -} - static void _mysql_ResultObject_dealloc( _mysql_ResultObject *self) @@ -2504,18 +2464,6 @@ static PyMethodDef _mysql_ResultObject_methods[] = { METH_VARARGS, _mysql_ResultObject_data_seek__doc__ }, - { - "row_seek", - (PyCFunction)_mysql_ResultObject_row_seek, - METH_VARARGS, - _mysql_ResultObject_row_seek__doc__ - }, - { - "row_tell", - (PyCFunction)_mysql_ResultObject_row_tell, - METH_NOARGS, - _mysql_ResultObject_row_tell__doc__ - }, { "describe", (PyCFunction)_mysql_ResultObject_describe, From 1ea5d01db84d68e45c2c25427fc70d8c28dbdc8c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 21 Dec 2017 19:16:24 +0900 Subject: [PATCH 091/396] my_bool is removed from MySQL 8 Fixed #218 --- _mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 5216fa5b..014c2bda 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1879,7 +1879,7 @@ _mysql_ConnectionObject_ping( if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; check_connection(self); if (reconnect != -1) { - my_bool recon = reconnect; + char recon = (char)reconnect; mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); } Py_BEGIN_ALLOW_THREADS From a2ebbd207375f545a8e97797b0c980313a58982f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 21 Dec 2017 20:09:58 +0900 Subject: [PATCH 092/396] Use _Bool only for MySQL 8 --- _mysql.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 014c2bda..24eacb33 100644 --- a/_mysql.c +++ b/_mysql.c @@ -29,6 +29,11 @@ PERFORMANCE OF THIS SOFTWARE. #include "mysql.h" #include "mysqld_error.h" +#if MYSQL_VERSION_ID >= 80000 +// https://github.com/mysql/mysql-server/commit/eb821c023cedc029ca0b06456dfae365106bee84 +#define my_bool _Bool +#endif + #include "Python.h" #if PY_MAJOR_VERSION >= 3 #define IS_PY3K @@ -1879,7 +1884,7 @@ _mysql_ConnectionObject_ping( if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; check_connection(self); if (reconnect != -1) { - char recon = (char)reconnect; + my_bool recon = (my_bool)reconnect; mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); } Py_BEGIN_ALLOW_THREADS From 1f8bd5361e068bc81a522ae405bb632d74d86be4 Mon Sep 17 00:00:00 2001 From: Vilnis Termanis Date: Sat, 20 Jan 2018 17:40:38 +0000 Subject: [PATCH 093/396] Reduce callproc roundtrip time - Make only one single call to SET variables used as procedure parameters --- MySQLdb/cursors.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 3769ab5e..b3632933 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -349,9 +349,10 @@ def callproc(self, procname, args=()): """ db = self._get_db() - for index, arg in enumerate(args): - q = "SET @_%s_%d=%s" % (procname, index, - db.literal(arg)) + if args: + argFmt = '@_{0}_%d=%s'.format(procname) + q = 'SET %s' % ','.join(argFmt % (index, db.literal(arg)) + for index, arg in enumerate(args)) if isinstance(q, unicode): q = q.encode(db.encoding, 'surrogateescape') self._query(q) From f2f94dcddeddbcd0b458873f6166b5d1dea23ba0 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 15 Mar 2018 18:44:22 +0900 Subject: [PATCH 094/396] Update cursors.py --- MySQLdb/cursors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index b3632933..bc1334b7 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -350,9 +350,9 @@ def callproc(self, procname, args=()): db = self._get_db() if args: - argFmt = '@_{0}_%d=%s'.format(procname) - q = 'SET %s' % ','.join(argFmt % (index, db.literal(arg)) - for index, arg in enumerate(args)) + fmt = '@_{0}_%d=%s'.format(procname) + q = 'SET %s' % ','.join(fmt % (index, db.literal(arg)) + for index, arg in enumerate(args)) if isinstance(q, unicode): q = q.encode(db.encoding, 'surrogateescape') self._query(q) From f95cfc09f6f9f569f6a9db93c39e8b56069b0b0e Mon Sep 17 00:00:00 2001 From: Lee Gaines Date: Thu, 22 Mar 2018 09:29:27 -0700 Subject: [PATCH 095/396] typo/grammar fix Changes "cursor execute" to "cursor executes" on line 388. --- doc/user_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 0ce88aee..86a8b17a 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -385,7 +385,7 @@ threadsafety SSCursor (which uses ``mysql_use_result()``; with the latter you must ensure all the rows have been read before another query can be executed. It is further complicated by the addition of - transactions, since transactions start when a cursor execute a + transactions, since transactions start when a cursor executes a query, but end when ``COMMIT`` or ``ROLLBACK`` is executed by the Connection object. Two threads simply cannot share a connection while a transaction is in progress, in addition to From 0ce8ed205d4d7c345f42f9c7e59d699bc4e2fc68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 15 May 2018 22:00:54 +0200 Subject: [PATCH 096/396] Use pytest instead of py.test per upstream recommendation, #dropthedot http://blog.pytest.org/2016/whats-new-in-pytest-30/ https://twitter.com/hashtag/dropthedot --- .travis.yml | 2 +- tests/test_cursor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 575f7e40..e1909524 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ before_script: script: - pip install -e . - - py.test --cov ./MySQLdb + - pytest --cov ./MySQLdb after_succes: - codecov diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 535afec3..7919b561 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,4 +1,4 @@ -import py.test +import pytest import MySQLdb.cursors from configdb import connection_factory From 07f4739d97396976cec3e29a306eba7c30f6a39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 15 May 2018 22:04:10 +0200 Subject: [PATCH 097/396] Spelling and grammar fixes --- MySQLdb/connections.py | 2 +- doc/user_guide.rst | 2 +- tests/dbapi20.py | 2 +- tests/test_MySQLdb_dbapi20.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 6d5f402e..8375cd0a 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -208,7 +208,7 @@ class object, used to create cursors (keyword only) self._server_version = tuple([ numeric_part(n) for n in self.get_server_info().split('.')[:2] ]) - self.encoding = 'ascii' # overriden in set_character_set() + self.encoding = 'ascii' # overridden in set_character_set() db = proxy(self) # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 86a8b17a..a0b752c3 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -8,7 +8,7 @@ MySQLdb User's Guide Introduction ------------ -MySQLdb is an thread-compatible interface to the popular MySQL +MySQLdb is a thread-compatible interface to the popular MySQL database server that provides the Python database API. Installation diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 19109f4d..e28d5d1f 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -633,7 +633,7 @@ def test_fetchall(self): cur.execute(sql) # cursor.fetchall should raise an Error if called - # after executing a a statement that cannot return rows + # after executing a statement that cannot return rows self.assertRaises(self.driver.Error,cur.fetchall) cur.execute('select name from %sbooze' % self.table_prefix) diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 2274c454..88eaaef8 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -35,7 +35,7 @@ def test_fetchall(self): cur.execute(sql) # cursor.fetchall should raise an Error if called - # after executing a a statement that cannot return rows + # after executing a statement that cannot return rows #self.assertRaises(self.driver.Error,cur.fetchall) cur.execute('select name from %sbooze' % self.table_prefix) From 46805facbc099a7c4887e2acf7bbea4fe0218459 Mon Sep 17 00:00:00 2001 From: "e. ravyn deadbunny" Date: Tue, 15 May 2018 17:37:58 -0700 Subject: [PATCH 098/396] Change default build option to use static linking for the MySQL client library, and support perconaserverclient as a valid link target. --- setup_posix.py | 14 ++++++++++---- site.cfg | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/setup_posix.py b/setup_posix.py index 14cffcad..216347e0 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -41,16 +41,12 @@ def get_config(): static = enabled(options, 'static') if enabled(options, 'embedded'): libs = mysql_config("libmysqld-libs") - client = "mysqld" elif enabled(options, 'threadsafe'): libs = mysql_config("libs_r") - client = "mysqlclient_r" if not libs: libs = mysql_config("libs") - client = "mysqlclient" else: libs = mysql_config("libs") - client = "mysqlclient" library_dirs = [dequote(i[2:]) for i in libs if i.startswith('-L')] libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')] @@ -68,6 +64,16 @@ def get_config(): include_dirs = [dequote(i[2:]) for i in mysql_config('include') if i.startswith('-I')] + + # properly handle mysql client libraries that are not called libmysqlclient + client = None + CLIENT_LIST = ['mysqlclient', 'mysqlclient_r', 'mysqld', + 'perconaserverclient', 'perconaserverclient_r'] + for c in CLIENT_LIST: + if c in libraries: + client = c + break + if static: extra_objects.append(os.path.join(library_dirs[0], 'lib%s.a' % client)) if client in libraries: diff --git a/site.cfg b/site.cfg index 8e2ab991..8afdef77 100644 --- a/site.cfg +++ b/site.cfg @@ -5,7 +5,7 @@ embedded = False threadsafe = True -static = False +static = True # The path to mysql_config. # Only use this if mysql_config is not on your PATH, or you have some weird From cacd5d0398e2d2e5c1540221928a15e6ec80c1b2 Mon Sep 17 00:00:00 2001 From: "E. Souhrada" Date: Tue, 15 May 2018 18:19:26 -0700 Subject: [PATCH 099/396] revert back to non-static linking by default --- site.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site.cfg b/site.cfg index 8afdef77..8e2ab991 100644 --- a/site.cfg +++ b/site.cfg @@ -5,7 +5,7 @@ embedded = False threadsafe = True -static = True +static = False # The path to mysql_config. # Only use this if mysql_config is not on your PATH, or you have some weird From 50fa27df5c857743e083452b5640dbc675919eab Mon Sep 17 00:00:00 2001 From: "E. Souhrada" Date: Tue, 15 May 2018 22:35:53 -0700 Subject: [PATCH 100/396] Add optional --static flag to customize _mysql.so at install time --- setup_posix.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/setup_posix.py b/setup_posix.py index 216347e0..6630a115 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -39,6 +39,14 @@ def get_config(): extra_objects = [] static = enabled(options, 'static') + + # allow a command-line option to override the base config file to permit + # a static build to be created via requirements.txt + # + if '--static' in sys.argv: + static = True + sys.argv.remove('--static') + if enabled(options, 'embedded'): libs = mysql_config("libmysqld-libs") elif enabled(options, 'threadsafe'): From 92cd53289376dbd1eff90a94b2810c4641205d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kukr=C3=A1l?= Date: Thu, 31 May 2018 10:38:54 +0200 Subject: [PATCH 101/396] use metapackage for libmysqlclient-dev (#245) libmysqliclient-dev package isn't available in Debian (Stretch) anymore. It's better to use metapackage for installation because it is available in Ubuntu and Debian. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2262e9d5..cd91383d 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ I hope this fork is merged back to MySQLdb1 like distribute was merged back to s You may need to install the Python and MySQL development headers and libraries like so: -* `sudo apt-get install python-dev libmysqlclient-dev` # Debian / Ubuntu +* `sudo apt-get install python-dev default-libmysqlclient-dev` # Debian / Ubuntu * `sudo yum install python-devel mysql-devel` # Red Hat / CentOS * `brew install mysql-connector-c` # macOS (Homebrew) (Currently, it has bug. See below) -On Windows, there are binary wheels you can install without MySQLConnector/C or MSVC. +On Windows, there are binary wheels you can install without MySQLConnector/C or MSVC. #### Note on Python 3 : if you are using python3 then you need to install python3-dev using the following command : @@ -31,21 +31,21 @@ See also: https://bugs.mysql.com/bug.php?id=86971 Versions of MySQL Connector/C may have incorrect default configuration options that cause compilation errors when `mysqlclient-python` is installed. (As of November 2017, this is known to be true for homebrew's `mysql-connector-c` and [official package](https://dev.mysql.com/downloads/connector/c/)) -Modification of `mysql_config` resolves these issues as follows. +Modification of `mysql_config` resolves these issues as follows. Change ``` # on macOS, on or about line 112: -# Create options +# Create options libs="-L$pkglibdir" libs="$libs -l " ``` -to +to ``` -# Create options +# Create options libs="-L$pkglibdir" libs="$libs -lmysqlclient -lssl -lcrypto" ``` From 99c1eea5782e5319aa512f567255c76d417c63e9 Mon Sep 17 00:00:00 2001 From: "E. Souhrada" Date: Wed, 27 Jun 2018 03:47:03 -0700 Subject: [PATCH 102/396] Use c++ for static link (#243) --- setup_posix.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup_posix.py b/setup_posix.py index 6630a115..6e409609 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -108,6 +108,11 @@ def get_config(): extra_objects = extra_objects, define_macros = define_macros, ) + + # newer versions of gcc require libstdc++ if doing a static build + if static: + ext_options['language'] = 'c++' + return metadata, ext_options if __name__ == "__main__": From 3de469db63c5da47d61ac79bba5dadc0d22bde5c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 27 Jun 2018 20:27:54 +0900 Subject: [PATCH 103/396] 1.3.13 --- HISTORY.rst | 13 +++++++++++++ MySQLdb/__init__.py | 8 ++------ metadata.cfg | 28 ++-------------------------- setup.py | 13 +++++++------ 4 files changed, 24 insertions(+), 38 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index b6aa8c2b..e37380b9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,16 @@ +====================== + What's new in 1.3.13 +====================== + +Support build with MySQL 8 + +Fix decoding tiny/medium/long blobs (#215) + +Remove broken row_seek() and row_tell() APIs (#220) + +Reduce callproc roundtrip time (#223) + + ====================== What's new in 1.3.12 ====================== diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 038309af..a4a9da94 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -1,4 +1,5 @@ -"""MySQLdb - A DB API v2.0 compatible interface to MySQL. +""" +MySQLdb - A DB API v2.0 compatible interface to MySQL. This package is a wrapper around _mysql, which mostly implements the MySQL C API. @@ -10,10 +11,8 @@ For information on how MySQLdb handles type conversion, see the MySQLdb.converters module. - """ -__revision__ = """$Revision$"""[11:-2] from MySQLdb.release import __version__, version_info, __author__ import _mysql @@ -98,6 +97,3 @@ def Connect(*args, **kwargs): 'escape_sequence', 'escape_string', 'get_client_info', 'paramstyle', 'string_literal', 'threadsafety', 'version_info'] - - - diff --git a/metadata.cfg b/metadata.cfg index cf40b262..4dee9616 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,30 +1,7 @@ [metadata] -version: 1.3.12 -version_info: (1,3,12,'final',0) +version: 1.3.13 +version_info: (1,3,13,'final',0) description: Python interface to MySQL -long_description: - ========================= - Python interface to MySQL - ========================= - \n - mysqlclient is a fork of MySQL-python. It adds Python 3 support - and fixed many bugs. - \n - MySQLdb is an interface to the popular MySQL_ database server for - Python. The design goals are: - \n - - Compliance with Python database API version 2.0 [PEP-0249]_ - - Thread-safety - - Thread-friendliness (threads will not block each other) - \n - MySQL-5.5 through 5.7 and Python 2.7, 3.4+ are currently supported. - PyPy is supported too. - \n - MySQLdb is `Free Software`_. - \n - .. _MySQL: http://www.mysql.com/ - .. _`Free Software`: http://www.gnu.org/ - .. [PEP-0249] https://www.python.org/dev/peps/pep-0249/ author: Andy Dustman author_email: farcepest@gmail.com maintainer: INADA Naoki @@ -47,7 +24,6 @@ classifiers: Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 diff --git a/setup.py b/setup.py index f88a3819..4d2e1b43 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,22 @@ #!/usr/bin/env python import os +import io import sys import distutils.errors import setuptools -if not hasattr(sys, "hexversion") or sys.hexversion < 0x02060000: - raise distutils.errors.DistutilsError("Python 2.6 or newer is required") - if os.name == "posix": from setup_posix import get_config else: # assume windows from setup_windows import get_config +with io.open('README.md', encoding='utf-8') as f: + readme = f.read() + metadata, options = get_config() -metadata['ext_modules'] = [ - setuptools.Extension(sources=['_mysql.c'], **options)] -metadata['long_description'] = metadata['long_description'].replace(r'\n', '') +metadata['ext_modules'] = [setuptools.Extension(sources=['_mysql.c'], **options)] +metadata['long_description'] = readme +metadata['long_description_content_type'] = "text/markdown" setuptools.setup(**metadata) From cfff0175bad7d63abca8a88b2fff811c2947c8d0 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 2 Oct 2018 06:04:51 +0100 Subject: [PATCH 104/396] Handle mariadb static library called mariadbclient.a (#265) --- setup_posix.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/setup_posix.py b/setup_posix.py index 6e409609..aa4b260b 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -72,17 +72,21 @@ def get_config(): include_dirs = [dequote(i[2:]) for i in mysql_config('include') if i.startswith('-I')] - - # properly handle mysql client libraries that are not called libmysqlclient - client = None - CLIENT_LIST = ['mysqlclient', 'mysqlclient_r', 'mysqld', - 'perconaserverclient', 'perconaserverclient_r'] - for c in CLIENT_LIST: - if c in libraries: - client = c - break - if static: + # properly handle mysql client libraries that are not called libmysqlclient + client = None + CLIENT_LIST = ['mysqlclient', 'mysqlclient_r', 'mysqld', 'mariadb', + 'perconaserverclient', 'perconaserverclient_r'] + for c in CLIENT_LIST: + if c in libraries: + client = c + break + + if client == 'mariadb': + client = 'mariadbclient' + if client is None: + raise ValueError("Couldn't identify mysql client library") + extra_objects.append(os.path.join(library_dirs[0], 'lib%s.a' % client)) if client in libraries: libraries.remove(client) From 3d4d286a10d264907a52347b58419050a785a27b Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Fri, 19 Oct 2018 15:50:23 +0500 Subject: [PATCH 105/396] Fixed float converter (#268) Use exponential notation always to MySQL treat it as float, not decimal. Use enough precision for round-trip value. --- MySQLdb/converters.py | 2 +- tests/capabilities.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 505a4df0..586f16e0 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -66,7 +66,7 @@ def Unicode2Str(s, d): return s.encode() def Float2Str(o, d): - return '%.15g' % o + return '%.16e' % o def None2NULL(o, d): """Convert None to NULL.""" diff --git a/tests/capabilities.py b/tests/capabilities.py index 31aa398e..4e02f173 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -279,3 +279,9 @@ def generator(row,col): ('col1 INT','col2 BLOB'), generator) + def test_DOUBLE(self): + for val in (18014398509481982.0, 0.1): + self.cursor.execute('SELECT %s', (val,)); + result = self.cursor.fetchone()[0] + self.assertEqual(result, val) + self.assertIsInstance(result, float) From 68b9662918577fc05be9610ef4824a00f2b051b0 Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Fri, 19 Oct 2018 15:51:36 +0500 Subject: [PATCH 106/396] Add Decimal converter (#267) --- MySQLdb/converters.py | 1 + tests/capabilities.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 586f16e0..b16aea50 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -139,5 +139,6 @@ def quote_tuple(t, d): from decimal import Decimal conversions[FIELD_TYPE.DECIMAL] = Decimal conversions[FIELD_TYPE.NEWDECIMAL] = Decimal + conversions[Decimal] = Thing2Str except ImportError: pass diff --git a/tests/capabilities.py b/tests/capabilities.py index 4e02f173..d4635b56 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -195,13 +195,18 @@ def generator(row,col): def test_DECIMAL(self): # DECIMAL + from decimal import Decimal def generator(row,col): - from decimal import Decimal return Decimal("%d.%02d" % (row, col)) self.check_data_integrity( ('col1 DECIMAL(5,2)',), generator) + self.cursor.execute('SELECT %s + %s', (Decimal('0.1'), Decimal('0.2'))) + result = self.cursor.fetchone()[0] + self.assertEqual(result, Decimal('0.3')) + self.assertIsInstance(result, Decimal) + def test_DATE(self): ticks = time() def generator(row,col): From 54c69436f45f2c78b3ace754780a1a265e60d430 Mon Sep 17 00:00:00 2001 From: Christian Ferrari <1506336+tiian@users.noreply.github.com> Date: Sat, 20 Oct 2018 05:20:44 +0200 Subject: [PATCH 107/396] Add Connection._get_native_connection Private API for XTA [1] project. [1]: http://www.tiian.org/lixa/XTA.html --- _mysql.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/_mysql.c b/_mysql.c index 24eacb33..a4f6976f 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1686,6 +1686,28 @@ _mysql_ConnectionObject_get_character_set_info( } #endif +static char _mysql_ConnectionObject_get_native_connection__doc__[] = +"Return the internal MYSQL* wrapped in a PyCapsule object.\n\ +NOTE: this is a private API introduced ONLY for XTA integration,\n\ + don't use it for different use cases.\n\ + This method is supported only for XTA integration and support must\n\ + be asked to LIXA project: http://www.tiian.org/lixa/\n\ + Please DO NOT ask support to PyMySQL/mysqlclient-python project.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_native_connection( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *result; + check_connection(self); + result = PyCapsule_New(&(self->connection), + "_mysql.connection.native_connection", NULL); + return result; +} + + static char _mysql_get_client_info__doc__[] = "get_client_info() -- Returns a string that represents\n\ the client library version."; @@ -2271,6 +2293,12 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { _mysql_ConnectionObject_get_character_set_info__doc__ }, #endif + { + "_get_native_connection", + (PyCFunction)_mysql_ConnectionObject_get_native_connection, + METH_NOARGS, + _mysql_ConnectionObject_get_native_connection__doc__ + }, { "close", (PyCFunction)_mysql_ConnectionObject_close, From 4a4978d6b10e6bc7983ad41f92277a635133f80f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 22 Oct 2018 21:26:57 +0900 Subject: [PATCH 108/396] Add missing checks for connection before calling mysql APIs (#272) Fixes #270 --- _mysql.c | 102 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/_mysql.c b/_mysql.c index a4f6976f..cea98203 100644 --- a/_mysql.c +++ b/_mysql.c @@ -69,9 +69,14 @@ typedef struct { PyObject *converter; } _mysql_ConnectionObject; -#define check_connection(c) if (!(c->open)) return _mysql_Exception(c) +#define check_connection(c, func) \ + if (!(c->open)) { \ + PyErr_SetString(_mysql_ProgrammingError, func "() is called for closed connection"); \ + return NULL; \ + }; + #define result_connection(r) ((_mysql_ConnectionObject *)r->conn) -#define check_result_connection(r) check_connection(result_connection(r)) +#define check_result_connection(r, func) check_connection(result_connection(r), func) extern PyTypeObject _mysql_ConnectionObject_Type; @@ -750,6 +755,7 @@ static PyObject * _mysql_ConnectionObject_fileno( _mysql_ConnectionObject *self) { + check_connection(self, "fileno"); return PyInt_FromLong(self->connection.net.fd); } @@ -761,16 +767,11 @@ _mysql_ConnectionObject_close( _mysql_ConnectionObject *self, PyObject *noargs) { - if (self->open) { - Py_BEGIN_ALLOW_THREADS - mysql_close(&(self->connection)); - Py_END_ALLOW_THREADS - self->open = 0; - } else { - PyErr_SetString(_mysql_ProgrammingError, - "closing a closed connection"); - return NULL; - } + check_connection(self, "close"); + Py_BEGIN_ALLOW_THREADS + mysql_close(&(self->connection)); + Py_END_ALLOW_THREADS + self->open = 0; _mysql_ConnectionObject_clear(self); Py_RETURN_NONE; } @@ -786,7 +787,7 @@ _mysql_ConnectionObject_affected_rows( PyObject *noargs) { my_ulonglong ret; - check_connection(self); + check_connection(self, "affected_rows"); ret = mysql_affected_rows(&(self->connection)); if (ret == (my_ulonglong)-1) return PyInt_FromLong(-1); @@ -823,7 +824,7 @@ _mysql_ConnectionObject_dump_debug_info( PyObject *noargs) { int err; - check_connection(self); + check_connection(self, "dump_debug_info"); Py_BEGIN_ALLOW_THREADS err = mysql_dump_debug_info(&(self->connection)); Py_END_ALLOW_THREADS @@ -842,6 +843,7 @@ _mysql_ConnectionObject_autocommit( { int flag, err; if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; + check_connection(self, "autocommit"); Py_BEGIN_ALLOW_THREADS err = mysql_autocommit(&(self->connection), flag); Py_END_ALLOW_THREADS @@ -858,6 +860,7 @@ _mysql_ConnectionObject_get_autocommit( _mysql_ConnectionObject *self, PyObject *args) { + check_connection(self, "get_autocommit"); if (self->connection.server_status & SERVER_STATUS_AUTOCOMMIT) { Py_RETURN_TRUE; } @@ -873,6 +876,7 @@ _mysql_ConnectionObject_commit( PyObject *noargs) { int err; + check_connection(self, "commit"); Py_BEGIN_ALLOW_THREADS err = mysql_commit(&(self->connection)); Py_END_ALLOW_THREADS @@ -890,13 +894,13 @@ _mysql_ConnectionObject_rollback( PyObject *noargs) { int err; + check_connection(self, "rollback"); Py_BEGIN_ALLOW_THREADS err = mysql_rollback(&(self->connection)); Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} + Py_RETURN_NONE; +} static char _mysql_ConnectionObject_next_result__doc__[] = "If more query results exist, next_result() reads the next query\n\ @@ -917,6 +921,7 @@ _mysql_ConnectionObject_next_result( PyObject *noargs) { int err; + check_connection(self, "next_result"); Py_BEGIN_ALLOW_THREADS err = mysql_next_result(&(self->connection)); Py_END_ALLOW_THREADS @@ -939,6 +944,7 @@ _mysql_ConnectionObject_set_server_option( int err, flags=0; if (!PyArg_ParseTuple(args, "i", &flags)) return NULL; + check_connection(self, "set_server_option"); Py_BEGIN_ALLOW_THREADS err = mysql_set_server_option(&(self->connection), flags); Py_END_ALLOW_THREADS @@ -963,6 +969,7 @@ _mysql_ConnectionObject_sqlstate( _mysql_ConnectionObject *self, PyObject *noargs) { + check_connection(self, "sqlstate"); return PyString_FromString(mysql_sqlstate(&(self->connection))); } @@ -977,6 +984,7 @@ _mysql_ConnectionObject_warning_count( _mysql_ConnectionObject *self, PyObject *noargs) { + check_connection(self, "warning_count"); return PyInt_FromLong(mysql_warning_count(&(self->connection))); } @@ -991,7 +999,7 @@ _mysql_ConnectionObject_errno( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self); + check_connection(self, "errno"); return PyInt_FromLong((long)mysql_errno(&(self->connection))); } @@ -1006,7 +1014,7 @@ _mysql_ConnectionObject_error( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self); + check_connection(self, "error"); return PyString_FromString(mysql_error(&(self->connection))); } @@ -1249,7 +1257,7 @@ _mysql_ResultObject_describe( PyObject *d; MYSQL_FIELD *fields; unsigned int i, n; - check_result_connection(self); + check_result_connection(self, "describe"); n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); if (!(d = PyTuple_New(n))) return NULL; @@ -1284,7 +1292,7 @@ _mysql_ResultObject_field_flags( PyObject *d; MYSQL_FIELD *fields; unsigned int i, n; - check_result_connection(self); + check_result_connection(self, "field_flags"); n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); if (!(d = PyTuple_New(n))) return NULL; @@ -1523,7 +1531,7 @@ _mysql_ResultObject_fetch_row( if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, &maxrows, &how)) return NULL; - check_result_connection(self); + check_result_connection(self, "fetch_row"); if (how >= (int)sizeof(row_converters)) { PyErr_SetString(PyExc_ValueError, "how out of range"); return NULL; @@ -1592,7 +1600,7 @@ _mysql_ConnectionObject_change_user( if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", kwlist, &user, &pwd, &db)) return NULL; - check_connection(self); + check_connection(self, "change_user"); Py_BEGIN_ALLOW_THREADS r = mysql_change_user(&(self->connection), user, pwd, db); Py_END_ALLOW_THREADS @@ -1612,7 +1620,7 @@ _mysql_ConnectionObject_character_set_name( PyObject *noargs) { const char *s; - check_connection(self); + check_connection(self, "character_set_name"); s = mysql_character_set_name(&(self->connection)); return PyString_FromString(s); } @@ -1630,7 +1638,7 @@ _mysql_ConnectionObject_set_character_set( const char *s; int err; if (!PyArg_ParseTuple(args, "s", &s)) return NULL; - check_connection(self); + check_connection(self, "set_character_set"); Py_BEGIN_ALLOW_THREADS err = mysql_set_character_set(&(self->connection), s); Py_END_ALLOW_THREADS @@ -1669,7 +1677,7 @@ _mysql_ConnectionObject_get_character_set_info( PyObject *result; MY_CHARSET_INFO cs; - check_connection(self); + check_connection(self, "get_character_set_info"); mysql_get_character_set_info(&(self->connection), &cs); if (!(result = PyDict_New())) return NULL; if (cs.csname) @@ -1701,7 +1709,7 @@ _mysql_ConnectionObject_get_native_connection( PyObject *noargs) { PyObject *result; - check_connection(self); + check_connection(self, "_get_native_connection"); result = PyCapsule_New(&(self->connection), "_mysql.connection.native_connection", NULL); return result; @@ -1730,7 +1738,7 @@ _mysql_ConnectionObject_get_host_info( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self); + check_connection(self, "get_host_info"); return PyString_FromString(mysql_get_host_info(&(self->connection))); } @@ -1744,7 +1752,7 @@ _mysql_ConnectionObject_get_proto_info( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self); + check_connection(self, "get_proto_info"); return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); } @@ -1758,7 +1766,7 @@ _mysql_ConnectionObject_get_server_info( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self); + check_connection(self, "get_server_info"); return PyString_FromString(mysql_get_server_info(&(self->connection))); } @@ -1774,7 +1782,7 @@ _mysql_ConnectionObject_info( PyObject *noargs) { const char *s; - check_connection(self); + check_connection(self, "info"); s = mysql_info(&(self->connection)); if (s) return PyString_FromString(s); Py_INCREF(Py_None); @@ -1808,7 +1816,7 @@ _mysql_ConnectionObject_insert_id( PyObject *noargs) { my_ulonglong r; - check_connection(self); + check_connection(self, "insert_id"); Py_BEGIN_ALLOW_THREADS r = mysql_insert_id(&(self->connection)); Py_END_ALLOW_THREADS @@ -1827,7 +1835,7 @@ _mysql_ConnectionObject_kill( unsigned long pid; int r; if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; - check_connection(self); + check_connection(self, "kill"); Py_BEGIN_ALLOW_THREADS r = mysql_kill(&(self->connection), pid); Py_END_ALLOW_THREADS @@ -1847,7 +1855,7 @@ _mysql_ConnectionObject_field_count( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self); + check_connection(self, "field_count"); return PyInt_FromLong((long)mysql_field_count(&(self->connection))); } @@ -1859,7 +1867,7 @@ _mysql_ResultObject_num_fields( _mysql_ResultObject *self, PyObject *noargs) { - check_result_connection(self); + check_result_connection(self, "num_fields"); return PyInt_FromLong((long)mysql_num_fields(self->result)); } @@ -1874,7 +1882,7 @@ _mysql_ResultObject_num_rows( _mysql_ResultObject *self, PyObject *noargs) { - check_result_connection(self); + check_result_connection(self, "num_rows"); return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); } @@ -1904,7 +1912,7 @@ _mysql_ConnectionObject_ping( { int r, reconnect = -1; if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; - check_connection(self); + check_connection(self, "ping"); if (reconnect != -1) { my_bool recon = (my_bool)reconnect; mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); @@ -1931,7 +1939,7 @@ _mysql_ConnectionObject_query( char *query; int len, r; if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self); + check_connection(self, "query"); Py_BEGIN_ALLOW_THREADS r = mysql_real_query(&(self->connection), query, len); @@ -1955,7 +1963,7 @@ _mysql_ConnectionObject_send_query( int len, r; MYSQL *mysql = &(self->connection); if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self); + check_connection(self, "send_query"); Py_BEGIN_ALLOW_THREADS r = mysql_send_query(mysql, query, len); @@ -1976,7 +1984,7 @@ _mysql_ConnectionObject_read_query_result( { int r; MYSQL *mysql = &(self->connection); - check_connection(self); + check_connection(self, "reqd_query_result"); Py_BEGIN_ALLOW_THREADS r = (int)mysql_read_query_result(mysql); @@ -2006,7 +2014,7 @@ _mysql_ConnectionObject_select_db( char *db; int r; if (!PyArg_ParseTuple(args, "s:select_db", &db)) return NULL; - check_connection(self); + check_connection(self, "select_db"); Py_BEGIN_ALLOW_THREADS r = mysql_select_db(&(self->connection), db); Py_END_ALLOW_THREADS @@ -2026,7 +2034,7 @@ _mysql_ConnectionObject_shutdown( PyObject *noargs) { int r; - check_connection(self); + check_connection(self, "shutdown"); Py_BEGIN_ALLOW_THREADS r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); Py_END_ALLOW_THREADS @@ -2048,7 +2056,7 @@ _mysql_ConnectionObject_stat( PyObject *noargs) { const char *s; - check_connection(self); + check_connection(self, "stat"); Py_BEGIN_ALLOW_THREADS s = mysql_stat(&(self->connection)); Py_END_ALLOW_THREADS @@ -2070,7 +2078,7 @@ _mysql_ConnectionObject_store_result( PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; _mysql_ResultObject *r=NULL; - check_connection(self); + check_connection(self, "store_result"); arglist = Py_BuildValue("(OiO)", self, 0, self->converter); if (!arglist) goto error; kwarglist = PyDict_New(); @@ -2108,7 +2116,7 @@ _mysql_ConnectionObject_thread_id( PyObject *noargs) { unsigned long pid; - check_connection(self); + check_connection(self, "thread_id"); Py_BEGIN_ALLOW_THREADS pid = mysql_thread_id(&(self->connection)); Py_END_ALLOW_THREADS @@ -2129,7 +2137,7 @@ _mysql_ConnectionObject_use_result( PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; _mysql_ResultObject *r=NULL; - check_connection(self); + check_connection(self, "use_result"); arglist = Py_BuildValue("(OiO)", self, 1, self->converter); if (!arglist) return NULL; kwarglist = PyDict_New(); @@ -2187,7 +2195,7 @@ _mysql_ResultObject_data_seek( { unsigned int row; if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; - check_result_connection(self); + check_result_connection(self, "data_seek"); mysql_data_seek(self->result, row); Py_INCREF(Py_None); return Py_None; From 777b74f53ec2d8fa0022cd7a137d0acde69cd61c Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Tue, 23 Oct 2018 12:16:52 +0500 Subject: [PATCH 109/396] Fix formatting and typo in user_guide.rst (#274) --- doc/user_guide.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index a0b752c3..82efe698 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -434,7 +434,7 @@ conv can be either: * a callable object which takes a string argument (the MySQL - value),' returning a Python value + value), returning a Python value * a sequence of 2-tuples, where the first value is a combination of flags from ``MySQLdb.constants.FLAG``, and the second value @@ -653,7 +653,6 @@ CursorTupleRowsMixIn Causes the cursor to return rows as a tuple of the column values. CursorDictRowsMixIn - Causes the cursor to return rows as a dictionary, where the keys are column names and the values are column values. Note that if the column names are not unique, i.e., you are selecting from two From 09018914e1f271afc1aee2c2f233a59032f9effc Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Tue, 23 Oct 2018 14:38:50 +0500 Subject: [PATCH 110/396] Use shorter format for float converter (#273) --- MySQLdb/converters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index b16aea50..04029cde 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -66,7 +66,10 @@ def Unicode2Str(s, d): return s.encode() def Float2Str(o, d): - return '%.16e' % o + s = repr(o) + if 'e' not in s: + s += 'e0' + return s def None2NULL(o, d): """Convert None to NULL.""" From 9fb618806bf638fa67f40b9cc3dafc4779237b63 Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Tue, 23 Oct 2018 15:04:18 +0500 Subject: [PATCH 111/396] Fix Connection.client_flag (#266) --- _mysql.c | 2 +- tests/test_MySQLdb_nonstandard.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index cea98203..ca991021 100644 --- a/_mysql.c +++ b/_mysql.c @@ -2491,8 +2491,8 @@ static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { { "client_flag", T_UINT, - READONLY, offsetof(_mysql_ConnectionObject,connection.client_flag), + READONLY, "Client flags; refer to MySQLdb.constants.CLIENT" }, {NULL} /* Sentinel */ diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index 7efe265c..52f6a4fb 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -92,3 +92,14 @@ def test_server_info(self): self.assertTrue(isinstance(self.conn.get_server_info(), str), "Should return an str.") + def test_client_flag(self): + conn = connection_factory( + use_unicode=True, + client_flag=MySQLdb.constants.CLIENT.FOUND_ROWS) + + self.assertIsInstance(conn.client_flag, (int, MySQLdb.compat.long)) + self.assertTrue(conn.client_flag & MySQLdb.constants.CLIENT.FOUND_ROWS) + with self.assertRaises(TypeError if MySQLdb.compat.PY2 else AttributeError): + conn.client_flag = 0 + + conn.close() From abcc4681fbde0fa2dc46f0b9a53788aff41aaa20 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 2 Nov 2018 20:42:12 +0900 Subject: [PATCH 112/396] Update INSTALL.rst (#281) --- INSTALL.rst | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 616b5902..e61af93c 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -8,40 +8,17 @@ MySQLdb Installation Prerequisites ------------- -+ Python 2.6, 2.7, 3.3 or higher - - * http://www.python.org/ ++ Python 2.7, 3.4 or higher + setuptools * http://pypi.python.org/pypi/setuptools -+ MySQL 5.0 or higher ++ MySQL 5.5 or higher * http://www.mysql.com/downloads/ - * MySQL-4.0 and MySQL-4.1 may work, but not supported. - - * MySQL-5.0 is supported and tested, including stored procedures. - - * MySQL-5.1 is supported (currently a release candidate) but untested. - It should work. - - * Red Hat Linux packages: - - - mysql-devel to compile - - - mysql and/or mysql-devel to run - - * MySQL.com RPM packages: - - - MySQL-devel to compile - - - MySQL-shared if you want to use their shared - library. Otherwise you'll get a statically-linked module, - which may or may not be what you want. - - - MySQL-shared to run if you compiled with MySQL-shared installed + * MySQL-5.0 may work, but not supported. + C compiler From e7fddaea248ed540ae9d19fc33aa75dbf2d37c50 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 5 Nov 2018 20:48:43 +0900 Subject: [PATCH 113/396] Clear cursor._query before query() (#283) --- MySQLdb/cursors.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index bc1334b7..9181f237 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -75,6 +75,7 @@ def __init__(self, connection): self._result = None self._warnings = None self.rownumber = None + self._rows = None def close(self): """Close the cursor. No further queries will be possible.""" @@ -172,23 +173,27 @@ def nextset(self): nr = db.next_result() if nr == -1: return None - self._do_get_result() + self._do_get_result(db) self._post_get_result() self._warning_check() return 1 - def _post_get_result(self): pass + def _do_get_result(self, db): + self._result = result = self._get_result() + if result is None: + self.description = self.description_flags = None + else: + self.description = result.describe() + self.description_flags = result.field_flags() - def _do_get_result(self): - db = self._get_db() - self._result = self._get_result() self.rowcount = db.affected_rows() self.rownumber = 0 - self.description = self._result and self._result.describe() or None - self.description_flags = self._result and self._result.field_flags() or None self.lastrowid = db.insert_id() self._warnings = None + def _post_get_result(self): + pass + def setinputsizes(self, *args): """Does nothing, required by DB API.""" @@ -248,7 +253,6 @@ def execute(self, query, args=None): except Exception: exc, value = sys.exc_info()[:2] self.errorhandler(self, exc, value) - self._executed = query if not self._defer_warnings: self._warning_check() return res @@ -364,21 +368,19 @@ def callproc(self, procname, args=()): if isinstance(q, unicode): q = q.encode(db.encoding, 'surrogateescape') self._query(q) - self._executed = q if not self._defer_warnings: self._warning_check() return args - def _do_query(self, q): + def _query(self, q): db = self._get_db() - self._last_executed = q + self._result = None db.query(q) - self._do_get_result() + self._do_get_result(db) + self._post_get_result() + self._executed = q return self.rowcount - def _query(self, q): - return self._do_query(q) - def _fetch_row(self, size=1): if not self._result: return () @@ -408,11 +410,6 @@ class CursorStoreResultMixIn(object): def _get_result(self): return self._get_db().store_result() - def _query(self, q): - rowcount = self._do_query(q) - self._post_get_result() - return rowcount - def _post_get_result(self): self._rows = self._fetch_row(0) self._result = None @@ -481,7 +478,8 @@ class CursorUseResultMixIn(object): _defer_warnings = True - def _get_result(self): return self._get_db().use_result() + def _get_result(self): + return self._get_db().use_result() def fetchone(self): """Fetches a single row from the cursor.""" From 413d3da22e099155c4221033ef84fe0ec73f2a10 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 5 Nov 2018 20:55:16 +0900 Subject: [PATCH 114/396] Update .travis.yml * Test with Python 3.7 * Use xenial --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1909524..a3b30d31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ -sudo: required -dist: trusty +dist: xenial language: python + +# See aws s3 ls s3://travis-python-archives/binaries/ubuntu/16.04/x86_64/ python: - "nightly" - - "pypy-5.3.1" - - "3.6-dev" + - "pypy3.5" + - "pypy2.7-5.10.0" + - "3.7" - "3.6" - "3.5" - "3.4" @@ -35,5 +37,4 @@ script: after_succes: - codecov - # vim: sw=2 ts=2 sts=2 From 53764264945fb26d5a21dd9000c4ea5eefdc6e2d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 5 Nov 2018 21:05:29 +0900 Subject: [PATCH 115/396] Return OperationalError for closed connection (#276) libmysqlclient returns CR_SERVER_GONE_ERROR(2006) when using closed connection. But libmariadbclient cause SEGV. For compatibility, emulate CR_SERVER_GONE_ERROR manually. Ref: GH-270 --- _mysql.c | 101 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/_mysql.c b/_mysql.c index ca991021..52f3283a 100644 --- a/_mysql.c +++ b/_mysql.c @@ -69,14 +69,13 @@ typedef struct { PyObject *converter; } _mysql_ConnectionObject; -#define check_connection(c, func) \ +#define check_connection(c) \ if (!(c->open)) { \ - PyErr_SetString(_mysql_ProgrammingError, func "() is called for closed connection"); \ - return NULL; \ + return _mysql_Exception(c); \ }; #define result_connection(r) ((_mysql_ConnectionObject *)r->conn) -#define check_result_connection(r, func) check_connection(result_connection(r), func) +#define check_result_connection(r) check_connection(result_connection(r)) extern PyTypeObject _mysql_ConnectionObject_Type; @@ -120,7 +119,15 @@ _mysql_Exception(_mysql_ConnectionObject *c) Py_DECREF(t); return NULL; } - merr = mysql_errno(&(c->connection)); + if (!(c->open)) { + /* GH-270: When connection is closed, accessing the c->connection + * object may cause SEGV. + */ + merr = CR_SERVER_GONE_ERROR; + } + else { + merr = mysql_errno(&(c->connection)); + } switch (merr) { case 0: e = _mysql_InterfaceError; @@ -755,7 +762,7 @@ static PyObject * _mysql_ConnectionObject_fileno( _mysql_ConnectionObject *self) { - check_connection(self, "fileno"); + check_connection(self); return PyInt_FromLong(self->connection.net.fd); } @@ -767,7 +774,7 @@ _mysql_ConnectionObject_close( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "close"); + check_connection(self); Py_BEGIN_ALLOW_THREADS mysql_close(&(self->connection)); Py_END_ALLOW_THREADS @@ -787,7 +794,7 @@ _mysql_ConnectionObject_affected_rows( PyObject *noargs) { my_ulonglong ret; - check_connection(self, "affected_rows"); + check_connection(self); ret = mysql_affected_rows(&(self->connection)); if (ret == (my_ulonglong)-1) return PyInt_FromLong(-1); @@ -824,7 +831,7 @@ _mysql_ConnectionObject_dump_debug_info( PyObject *noargs) { int err; - check_connection(self, "dump_debug_info"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_dump_debug_info(&(self->connection)); Py_END_ALLOW_THREADS @@ -843,7 +850,7 @@ _mysql_ConnectionObject_autocommit( { int flag, err; if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; - check_connection(self, "autocommit"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_autocommit(&(self->connection), flag); Py_END_ALLOW_THREADS @@ -860,7 +867,7 @@ _mysql_ConnectionObject_get_autocommit( _mysql_ConnectionObject *self, PyObject *args) { - check_connection(self, "get_autocommit"); + check_connection(self); if (self->connection.server_status & SERVER_STATUS_AUTOCOMMIT) { Py_RETURN_TRUE; } @@ -876,7 +883,7 @@ _mysql_ConnectionObject_commit( PyObject *noargs) { int err; - check_connection(self, "commit"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_commit(&(self->connection)); Py_END_ALLOW_THREADS @@ -894,7 +901,7 @@ _mysql_ConnectionObject_rollback( PyObject *noargs) { int err; - check_connection(self, "rollback"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_rollback(&(self->connection)); Py_END_ALLOW_THREADS @@ -921,7 +928,7 @@ _mysql_ConnectionObject_next_result( PyObject *noargs) { int err; - check_connection(self, "next_result"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_next_result(&(self->connection)); Py_END_ALLOW_THREADS @@ -944,7 +951,7 @@ _mysql_ConnectionObject_set_server_option( int err, flags=0; if (!PyArg_ParseTuple(args, "i", &flags)) return NULL; - check_connection(self, "set_server_option"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_set_server_option(&(self->connection), flags); Py_END_ALLOW_THREADS @@ -969,7 +976,7 @@ _mysql_ConnectionObject_sqlstate( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "sqlstate"); + check_connection(self); return PyString_FromString(mysql_sqlstate(&(self->connection))); } @@ -984,7 +991,7 @@ _mysql_ConnectionObject_warning_count( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "warning_count"); + check_connection(self); return PyInt_FromLong(mysql_warning_count(&(self->connection))); } @@ -999,7 +1006,7 @@ _mysql_ConnectionObject_errno( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "errno"); + check_connection(self); return PyInt_FromLong((long)mysql_errno(&(self->connection))); } @@ -1014,7 +1021,7 @@ _mysql_ConnectionObject_error( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "error"); + check_connection(self); return PyString_FromString(mysql_error(&(self->connection))); } @@ -1257,7 +1264,7 @@ _mysql_ResultObject_describe( PyObject *d; MYSQL_FIELD *fields; unsigned int i, n; - check_result_connection(self, "describe"); + check_result_connection(self); n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); if (!(d = PyTuple_New(n))) return NULL; @@ -1292,7 +1299,7 @@ _mysql_ResultObject_field_flags( PyObject *d; MYSQL_FIELD *fields; unsigned int i, n; - check_result_connection(self, "field_flags"); + check_result_connection(self); n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); if (!(d = PyTuple_New(n))) return NULL; @@ -1531,7 +1538,7 @@ _mysql_ResultObject_fetch_row( if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, &maxrows, &how)) return NULL; - check_result_connection(self, "fetch_row"); + check_result_connection(self); if (how >= (int)sizeof(row_converters)) { PyErr_SetString(PyExc_ValueError, "how out of range"); return NULL; @@ -1600,7 +1607,7 @@ _mysql_ConnectionObject_change_user( if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", kwlist, &user, &pwd, &db)) return NULL; - check_connection(self, "change_user"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_change_user(&(self->connection), user, pwd, db); Py_END_ALLOW_THREADS @@ -1620,7 +1627,7 @@ _mysql_ConnectionObject_character_set_name( PyObject *noargs) { const char *s; - check_connection(self, "character_set_name"); + check_connection(self); s = mysql_character_set_name(&(self->connection)); return PyString_FromString(s); } @@ -1638,7 +1645,7 @@ _mysql_ConnectionObject_set_character_set( const char *s; int err; if (!PyArg_ParseTuple(args, "s", &s)) return NULL; - check_connection(self, "set_character_set"); + check_connection(self); Py_BEGIN_ALLOW_THREADS err = mysql_set_character_set(&(self->connection), s); Py_END_ALLOW_THREADS @@ -1677,7 +1684,7 @@ _mysql_ConnectionObject_get_character_set_info( PyObject *result; MY_CHARSET_INFO cs; - check_connection(self, "get_character_set_info"); + check_connection(self); mysql_get_character_set_info(&(self->connection), &cs); if (!(result = PyDict_New())) return NULL; if (cs.csname) @@ -1709,7 +1716,7 @@ _mysql_ConnectionObject_get_native_connection( PyObject *noargs) { PyObject *result; - check_connection(self, "_get_native_connection"); + check_connection(self); result = PyCapsule_New(&(self->connection), "_mysql.connection.native_connection", NULL); return result; @@ -1738,7 +1745,7 @@ _mysql_ConnectionObject_get_host_info( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "get_host_info"); + check_connection(self); return PyString_FromString(mysql_get_host_info(&(self->connection))); } @@ -1752,7 +1759,7 @@ _mysql_ConnectionObject_get_proto_info( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "get_proto_info"); + check_connection(self); return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); } @@ -1766,7 +1773,7 @@ _mysql_ConnectionObject_get_server_info( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "get_server_info"); + check_connection(self); return PyString_FromString(mysql_get_server_info(&(self->connection))); } @@ -1782,7 +1789,7 @@ _mysql_ConnectionObject_info( PyObject *noargs) { const char *s; - check_connection(self, "info"); + check_connection(self); s = mysql_info(&(self->connection)); if (s) return PyString_FromString(s); Py_INCREF(Py_None); @@ -1816,7 +1823,7 @@ _mysql_ConnectionObject_insert_id( PyObject *noargs) { my_ulonglong r; - check_connection(self, "insert_id"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_insert_id(&(self->connection)); Py_END_ALLOW_THREADS @@ -1835,7 +1842,7 @@ _mysql_ConnectionObject_kill( unsigned long pid; int r; if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; - check_connection(self, "kill"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_kill(&(self->connection), pid); Py_END_ALLOW_THREADS @@ -1855,7 +1862,7 @@ _mysql_ConnectionObject_field_count( _mysql_ConnectionObject *self, PyObject *noargs) { - check_connection(self, "field_count"); + check_connection(self); return PyInt_FromLong((long)mysql_field_count(&(self->connection))); } @@ -1867,7 +1874,7 @@ _mysql_ResultObject_num_fields( _mysql_ResultObject *self, PyObject *noargs) { - check_result_connection(self, "num_fields"); + check_result_connection(self); return PyInt_FromLong((long)mysql_num_fields(self->result)); } @@ -1882,7 +1889,7 @@ _mysql_ResultObject_num_rows( _mysql_ResultObject *self, PyObject *noargs) { - check_result_connection(self, "num_rows"); + check_result_connection(self); return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); } @@ -1912,7 +1919,7 @@ _mysql_ConnectionObject_ping( { int r, reconnect = -1; if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; - check_connection(self, "ping"); + check_connection(self); if (reconnect != -1) { my_bool recon = (my_bool)reconnect; mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); @@ -1939,7 +1946,7 @@ _mysql_ConnectionObject_query( char *query; int len, r; if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self, "query"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_real_query(&(self->connection), query, len); @@ -1963,7 +1970,7 @@ _mysql_ConnectionObject_send_query( int len, r; MYSQL *mysql = &(self->connection); if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self, "send_query"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_send_query(mysql, query, len); @@ -1984,7 +1991,7 @@ _mysql_ConnectionObject_read_query_result( { int r; MYSQL *mysql = &(self->connection); - check_connection(self, "reqd_query_result"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = (int)mysql_read_query_result(mysql); @@ -2014,7 +2021,7 @@ _mysql_ConnectionObject_select_db( char *db; int r; if (!PyArg_ParseTuple(args, "s:select_db", &db)) return NULL; - check_connection(self, "select_db"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_select_db(&(self->connection), db); Py_END_ALLOW_THREADS @@ -2034,7 +2041,7 @@ _mysql_ConnectionObject_shutdown( PyObject *noargs) { int r; - check_connection(self, "shutdown"); + check_connection(self); Py_BEGIN_ALLOW_THREADS r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); Py_END_ALLOW_THREADS @@ -2056,7 +2063,7 @@ _mysql_ConnectionObject_stat( PyObject *noargs) { const char *s; - check_connection(self, "stat"); + check_connection(self); Py_BEGIN_ALLOW_THREADS s = mysql_stat(&(self->connection)); Py_END_ALLOW_THREADS @@ -2078,7 +2085,7 @@ _mysql_ConnectionObject_store_result( PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; _mysql_ResultObject *r=NULL; - check_connection(self, "store_result"); + check_connection(self); arglist = Py_BuildValue("(OiO)", self, 0, self->converter); if (!arglist) goto error; kwarglist = PyDict_New(); @@ -2116,7 +2123,7 @@ _mysql_ConnectionObject_thread_id( PyObject *noargs) { unsigned long pid; - check_connection(self, "thread_id"); + check_connection(self); Py_BEGIN_ALLOW_THREADS pid = mysql_thread_id(&(self->connection)); Py_END_ALLOW_THREADS @@ -2137,7 +2144,7 @@ _mysql_ConnectionObject_use_result( PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; _mysql_ResultObject *r=NULL; - check_connection(self, "use_result"); + check_connection(self); arglist = Py_BuildValue("(OiO)", self, 1, self->converter); if (!arglist) return NULL; kwarglist = PyDict_New(); @@ -2195,7 +2202,7 @@ _mysql_ResultObject_data_seek( { unsigned int row; if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; - check_result_connection(self, "data_seek"); + check_result_connection(self); mysql_data_seek(self->result, row); Py_INCREF(Py_None); return Py_None; From 21170a763941d6f9cd736f6060ccef94358a326d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 5 Nov 2018 21:28:05 +0900 Subject: [PATCH 116/396] Deprecate waiter (#285) --- MySQLdb/connections.py | 14 ++++++++------ _mysql.c | 2 +- samples/waiter_gevent.py | 24 ------------------------ samples/waiter_meinheld.py | 19 ------------------- 4 files changed, 9 insertions(+), 50 deletions(-) delete mode 100644 samples/waiter_gevent.py delete mode 100644 samples/waiter_meinheld.py diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 8375cd0a..10a0bf77 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -200,6 +200,10 @@ class object, used to create cursors (keyword only) # PEP-249 requires autocommit to be initially off autocommit = kwargs2.pop('autocommit', False) self.waiter = kwargs2.pop('waiter', None) + if self.waiter: + from warnings import warn + warn("waiter is deprecated and will be removed in 1.4.", + DeprecationWarning, 2) super(Connection, self).__init__(*args, **kwargs2) self.cursorclass = cursorclass @@ -325,12 +329,10 @@ def literal(self, o): return s def begin(self): - """Explicitly begin a connection. Non-standard. - DEPRECATED: Will be removed in 1.3. - Use an SQL BEGIN statement instead.""" - from warnings import warn - warn("begin() is non-standard and will be removed in 1.4", - DeprecationWarning, 2) + """Explicitly begin a connection. + + This method is not used when autocommit=False (default). + """ self.query("BEGIN") if not hasattr(_mysql.connection, 'warning_count'): diff --git a/_mysql.c b/_mysql.c index 52f3283a..bf85704e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -756,7 +756,7 @@ static int _mysql_ConnectionObject_clear( } static char _mysql_ConnectionObject_fileno__doc__[] = -"Return underlaying fd for connection"; +"Return underlaying fd for connection (deprecated)"; static PyObject * _mysql_ConnectionObject_fileno( diff --git a/samples/waiter_gevent.py b/samples/waiter_gevent.py deleted file mode 100644 index 698b9158..00000000 --- a/samples/waiter_gevent.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import print_function -"""Demo using Gevent with mysqlclient.""" - -import gevent.hub -import MySQLdb - - -def gevent_waiter(fd, hub=gevent.hub.get_hub()): - hub.wait(hub.loop.io(fd, 1)) - - -def f(n): - conn = MySQLdb.connect(user='root', waiter=gevent_waiter) - cur = conn.cursor() - cur.execute("SELECT SLEEP(%s)", (n,)) - cur.execute("SELECT 1+%s", (n,)) - print(cur.fetchall()[0]) - - -gevent.spawn(f, 1) -gevent.spawn(f, 2) -gevent.spawn(f, 3) -gevent.spawn(f, 4) -gevent.sleep(5) diff --git a/samples/waiter_meinheld.py b/samples/waiter_meinheld.py deleted file mode 100644 index 838c9153..00000000 --- a/samples/waiter_meinheld.py +++ /dev/null @@ -1,19 +0,0 @@ -import meinheld -import MySQLdb - - -def meinheld_waiter(fd): - meinheld.server.trampoline(fd, read=True, timeout=10) - - -def app(env, start): - cont = b"Hello, World\n" - conn = MySQLdb.connect(user="root", waiter=meinheld_waiter) - cur = conn.cursor() - cur.execute("SELECT SLEEP(2)") - start(b"200 OK", [('Content-Type', 'text/plain'), ('Content-Length', str(len(cont)))]) - return [cont] - - -meinheld.server.listen(("0.0.0.0", 8080)) -meinheld.server.run(app) From 9f71220b9271109a7ec0b8d858a210d6fe7d742a Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Wed, 7 Nov 2018 12:46:02 +0500 Subject: [PATCH 117/396] Fix conversion of decimals with exponent (#286) --- MySQLdb/converters.py | 4 +++- tests/capabilities.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 04029cde..5e28005d 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -82,6 +82,8 @@ def Thing2Literal(o, d): that method when the connection is created.""" return string_literal(o, d) +def Decimal2Literal(o, d): + return format(o, 'f') def char_array(s): return array.array('c', s) @@ -142,6 +144,6 @@ def quote_tuple(t, d): from decimal import Decimal conversions[FIELD_TYPE.DECIMAL] = Decimal conversions[FIELD_TYPE.NEWDECIMAL] = Decimal - conversions[Decimal] = Thing2Str + conversions[Decimal] = Decimal2Literal except ImportError: pass diff --git a/tests/capabilities.py b/tests/capabilities.py index d4635b56..c341b3c9 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -202,6 +202,12 @@ def generator(row,col): ('col1 DECIMAL(5,2)',), generator) + val = Decimal('1.11111111111111119E-7') + self.cursor.execute('SELECT %s', (val,)) + result = self.cursor.fetchone()[0] + self.assertEqual(result, val) + self.assertIsInstance(result, Decimal) + self.cursor.execute('SELECT %s + %s', (Decimal('0.1'), Decimal('0.2'))) result = self.cursor.fetchone()[0] self.assertEqual(result, Decimal('0.3')) From 01f820991158a2f6c13c2fc88a7c55f21be6282f Mon Sep 17 00:00:00 2001 From: Gatsby Lee Date: Thu, 8 Nov 2018 23:12:45 -0800 Subject: [PATCH 118/396] Fix typo in _mysql.c (#287) --- _mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index bf85704e..71f4509c 100644 --- a/_mysql.c +++ b/_mysql.c @@ -99,7 +99,7 @@ static int _mysql_server_init_done = 0; #endif /* According to https://dev.mysql.com/doc/refman/5.1/en/mysql-options.html - The MYSQL_OPT_READ_TIMEOUT apear in the version 5.1.12 */ + The MYSQL_OPT_READ_TIMEOUT appear in the version 5.1.12 */ #if MYSQL_VERSION_ID > 50112 #define HAVE_MYSQL_OPT_TIMEOUTS 1 #endif From 45bf8797d1bd90eb1ea2db577ba8c888b3c89099 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 4 Dec 2018 18:23:35 +0900 Subject: [PATCH 119/396] Remove HAVE_OPENSSL (#292) Assume ssl is always available. --- _mysql.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/_mysql.c b/_mysql.c index 71f4509c..3ee26c8b 100644 --- a/_mysql.c +++ b/_mysql.c @@ -94,10 +94,6 @@ extern PyTypeObject _mysql_ResultObject_Type; static int _mysql_server_init_done = 0; #define check_server_init(x) if (!_mysql_server_init_done) { if (mysql_server_init(0, NULL, NULL)) { _mysql_Exception(NULL); return x; } else { _mysql_server_init_done = 1;} } -#if MYSQL_VERSION_ID >= 50500 -#define HAVE_OPENSSL 1 -#endif - /* According to https://dev.mysql.com/doc/refman/5.1/en/mysql-options.html The MYSQL_OPT_READ_TIMEOUT appear in the version 5.1.12 */ #if MYSQL_VERSION_ID > 50112 @@ -502,12 +498,10 @@ _mysql_ConnectionObject_Initialize( MYSQL *conn = NULL; PyObject *conv = NULL; PyObject *ssl = NULL; -#if HAVE_OPENSSL char *key = NULL, *cert = NULL, *ca = NULL, *capath = NULL, *cipher = NULL; PyObject *ssl_keepref[5] = {NULL}; int n_ssl_keepref = 0; -#endif char *host = NULL, *user = NULL, *passwd = NULL, *db = NULL, *unix_socket = NULL; unsigned int port = 0; @@ -571,18 +565,12 @@ _mysql_ConnectionObject_Initialize( #endif if (ssl) { -#if HAVE_OPENSSL PyObject *value = NULL; _stringsuck(ca, value, ssl); _stringsuck(capath, value, ssl); _stringsuck(cert, value, ssl); _stringsuck(key, value, ssl); _stringsuck(cipher, value, ssl); -#else - PyErr_SetString(_mysql_NotSupportedError, - "client library does not have SSL support"); - return -1; -#endif } Py_BEGIN_ALLOW_THREADS ; @@ -620,18 +608,15 @@ _mysql_ConnectionObject_Initialize( if (local_infile != -1) mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); -#if HAVE_OPENSSL if (ssl) { mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); } -#endif conn = mysql_real_connect(&(self->connection), host, user, passwd, db, port, unix_socket, client_flag); Py_END_ALLOW_THREADS ; -#if HAVE_OPENSSL if (ssl) { int i; for (i=0; i Date: Tue, 4 Dec 2018 19:05:25 +0900 Subject: [PATCH 120/396] 1.3.14 --- HISTORY.rst | 24 ++++++++++++++++++++++++ metadata.cfg | 5 +++-- setup.py | 2 -- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e37380b9..dd3bbaeb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,27 @@ +====================== + What's new in 1.3.14 +====================== + +Release: 2018-12-04 + +* Support static linking of MariaDB Connector/C (#265) + +* Better converter for Decimal and Float (#267, #268, #273, #286) + +* Add ``Connection._get_native_connection`` for XTA project (#269) + +* Fix SEGV on MariaDB Connector/C when ``Connection.close()`` is called + for closed connection. (#270, #272, #276) + +* Fix ``Connection.client_flag`` (#266) + +* Fix SSCursor may raise same exception twice (#282) + +* ``waiter`` option is now deprecated. (#285) + +* Fixed SSL support is not detected when built with MySQL < 5.1 (#291) + + ====================== What's new in 1.3.13 ====================== diff --git a/metadata.cfg b/metadata.cfg index 4dee9616..6e05d35d 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.3.13 -version_info: (1,3,13,'final',0) +version: 1.3.14 +version_info: (1,3,14,'final',0) description: Python interface to MySQL author: Andy Dustman author_email: farcepest@gmail.com @@ -27,6 +27,7 @@ classifiers: Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: diff --git a/setup.py b/setup.py index 4d2e1b43..ca4b362e 100644 --- a/setup.py +++ b/setup.py @@ -2,9 +2,7 @@ import os import io -import sys -import distutils.errors import setuptools if os.name == "posix": From 354b60145df8c4edfdc70af8b88080eabbf25d35 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 29 Oct 2018 19:33:43 +0900 Subject: [PATCH 121/396] Remove threadsafe and embedded build options --- INSTALL.rst | 23 ++----- _mysql.c | 149 +-------------------------------------------- doc/FAQ.rst | 7 --- doc/user_guide.rst | 27 -------- setup_posix.py | 23 +++---- site.cfg | 7 +-- 6 files changed, 15 insertions(+), 221 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index e61af93c..2588b99e 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -42,20 +42,10 @@ Depending on which version of MySQL you have, you may have the option of using three different client libraries. To select the client library, edit the [options] section of site.cfg: - embedded - use embedded server library (libmysqld) if True; otherwise use - one of the client libraries (default). - - threadsafe - thread-safe client library (libmysqlclient_r) if True (default); - otherwise use non-thread-safe (libmysqlclient). You should - always use the thread-safe library if you have the option; - otherwise you *may* have problems. - static if True, try to link against a static library; otherwise link - against dynamic libraries (default). You may need static linking - to use the embedded server. + against dynamic libraries (default). You may need static linking + to use the embedded server. This option doesn't work for MySQL>5.6 since libmysqlclient requires libstdc++. If you want to use, add `-lstdc++` to mysql_config manually. @@ -130,7 +120,7 @@ Debian GNU/Linux Packaged as `python-mysqldb`_:: - # apt-get install python-mysqldb + # apt-get install python-mysqldb Or use Synaptic. @@ -148,9 +138,9 @@ Gentoo Linux Packaged as `mysql-python`_. :: - # emerge sync - # emerge mysql-python - # emerge zmysqlda # if you use Zope + # emerge sync + # emerge mysql-python + # emerge zmysqlda # if you use Zope .. _`mysql-python`: https://packages.gentoo.org/packages/search?q=mysql-python @@ -169,4 +159,3 @@ GPL or the original license based on Python 1.5.2's license. :Author: Andy Dustman -:Revision: $Id$ diff --git a/_mysql.c b/_mysql.c index 3ee26c8b..430b149b 100644 --- a/_mysql.c +++ b/_mysql.c @@ -91,8 +91,6 @@ typedef struct { extern PyTypeObject _mysql_ResultObject_Type; -static int _mysql_server_init_done = 0; -#define check_server_init(x) if (!_mysql_server_init_done) { if (mysql_server_init(0, NULL, NULL)) { _mysql_Exception(NULL); return x; } else { _mysql_server_init_done = 1;} } /* According to https://dev.mysql.com/doc/refman/5.1/en/mysql-options.html The MYSQL_OPT_READ_TIMEOUT appear in the version 5.1.12 */ @@ -107,14 +105,6 @@ _mysql_Exception(_mysql_ConnectionObject *c) int merr; if (!(t = PyTuple_New(2))) return NULL; - if (!_mysql_server_init_done) { - e = _mysql_InternalError; - PyTuple_SET_ITEM(t, 0, PyInt_FromLong(-1L)); - PyTuple_SET_ITEM(t, 1, PyString_FromString("server not initialized")); - PyErr_SetObject(e, t); - Py_DECREF(t); - return NULL; - } if (!(c->open)) { /* GH-270: When connection is closed, accessing the c->connection * object may cause SEGV. @@ -219,124 +209,6 @@ _mysql_Exception(_mysql_ConnectionObject *c) return NULL; } -static char _mysql_server_init__doc__[] = -"Initialize embedded server. If this client is not linked against\n\ -the embedded server library, this function does nothing.\n\ -\n\ -args -- sequence of command-line arguments\n\ -groups -- sequence of groups to use in defaults files\n\ -"; - -static PyObject *_mysql_server_init( - PyObject *self, - PyObject *args, - PyObject *kwargs) { - static char *kwlist[] = {"args", "groups", NULL}; - char **cmd_args_c=NULL, **groups_c=NULL, *s; - int cmd_argc=0, i, groupc; - PyObject *cmd_args=NULL, *groups=NULL, *ret=NULL, *item; - - if (_mysql_server_init_done) { - PyErr_SetString(_mysql_ProgrammingError, - "already initialized"); - return NULL; - } - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", kwlist, - &cmd_args, &groups)) - return NULL; - - if (cmd_args) { - if (!PySequence_Check(cmd_args)) { - PyErr_SetString(PyExc_TypeError, - "args must be a sequence"); - goto finish; - } - cmd_argc = PySequence_Size(cmd_args); - if (cmd_argc == -1) { - PyErr_SetString(PyExc_TypeError, - "args could not be sized"); - goto finish; - } - cmd_args_c = (char **) PyMem_Malloc(cmd_argc*sizeof(char *)); - for (i=0; i< cmd_argc; i++) { - item = PySequence_GetItem(cmd_args, i); -#ifdef IS_PY3K - s = PyUnicode_AsUTF8(item); -#else - s = PyString_AsString(item); -#endif - - Py_DECREF(item); - if (!s) { - PyErr_SetString(PyExc_TypeError, - "args must contain strings"); - goto finish; - } - cmd_args_c[i] = s; - } - } - if (groups) { - if (!PySequence_Check(groups)) { - PyErr_SetString(PyExc_TypeError, - "groups must be a sequence"); - goto finish; - } - groupc = PySequence_Size(groups); - if (groupc == -1) { - PyErr_SetString(PyExc_TypeError, - "groups could not be sized"); - goto finish; - } - groups_c = (char **) PyMem_Malloc((1+groupc)*sizeof(char *)); - for (i=0; i< groupc; i++) { - item = PySequence_GetItem(groups, i); -#ifdef IS_PY3K - s = PyUnicode_AsUTF8(item); -#else - s = PyString_AsString(item); -#endif - Py_DECREF(item); - if (!s) { - PyErr_SetString(PyExc_TypeError, - "groups must contain strings"); - goto finish; - } - groups_c[i] = s; - } - groups_c[groupc] = (char *)NULL; - } - /* even though this may block, don't give up the interpreter lock - so that the server can't be initialized multiple times. */ - if (mysql_server_init(cmd_argc, cmd_args_c, groups_c)) { - _mysql_Exception(NULL); - goto finish; - } - ret = Py_None; - Py_INCREF(Py_None); - _mysql_server_init_done = 1; - finish: - PyMem_Free(groups_c); - PyMem_Free(cmd_args_c); - return ret; -} - -static char _mysql_server_end__doc__[] = -"Shut down embedded server. If not using an embedded server, this\n\ -does nothing."; - -static PyObject *_mysql_server_end( - PyObject *self, - PyObject *args) { - if (_mysql_server_init_done) { - mysql_server_end(); - _mysql_server_init_done = 0; - Py_INCREF(Py_None); - return Py_None; - } - return _mysql_Exception(NULL); -} - static char _mysql_thread_safe__doc__[] = "Indicates whether the client is compiled as thread-safe."; @@ -344,10 +216,7 @@ static PyObject *_mysql_thread_safe( PyObject *self, PyObject *noargs) { - PyObject *flag; - check_server_init(NULL); - if (!(flag=PyInt_FromLong((long)mysql_thread_safe()))) return NULL; - return flag; + return PyInt_FromLong((long)mysql_thread_safe()); } static char _mysql_ResultObject__doc__[] = @@ -530,7 +399,6 @@ _mysql_ConnectionObject_Initialize( self->converter = NULL; self->open = 0; - check_server_init(-1); if (!PyArg_ParseTupleAndKeywords(args, kwargs, #ifdef HAVE_MYSQL_OPT_TIMEOUTS @@ -1029,7 +897,6 @@ _mysql_escape_string( str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); if (!str) return PyErr_NoMemory(); out = PyBytes_AS_STRING(str); - check_server_init(NULL); if (self && PyModule_Check((PyObject*)self)) self = NULL; @@ -1090,7 +957,6 @@ _mysql_string_literal( return PyErr_NoMemory(); } out = PyBytes_AS_STRING(str); - check_server_init(NULL); if (self && self->open) { #if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); @@ -1715,7 +1581,6 @@ _mysql_get_client_info( PyObject *self, PyObject *noargs) { - check_server_init(NULL); return PyString_FromString(mysql_get_client_info()); } @@ -2785,18 +2650,6 @@ _mysql_methods[] = { METH_NOARGS, _mysql_thread_safe__doc__ }, - { - "server_init", - (PyCFunction)_mysql_server_init, - METH_VARARGS | METH_KEYWORDS, - _mysql_server_init__doc__ - }, - { - "server_end", - (PyCFunction)_mysql_server_end, - METH_VARARGS, - _mysql_server_end__doc__ - }, {NULL, NULL} /* sentinel */ }; diff --git a/doc/FAQ.rst b/doc/FAQ.rst index 59c6b93d..21e00b9b 100644 --- a/doc/FAQ.rst +++ b/doc/FAQ.rst @@ -9,13 +9,6 @@ Build Errors ------------ - ld: fatal: library -lmysqlclient_r: not found - -mysqlclient_r is the thread-safe library. It's not available on -all platforms, or all installations, apparently. You'll need to -reconfigure site.cfg (in MySQLdb-1.2.1 and newer) to have -threadsafe = False. - mysql.h: No such file or directory This almost always mean you don't have development packages diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 82efe698..d686b786 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -680,33 +680,6 @@ SSDictCursor Like ``SSCursor`` except it returns rows as dictionaries. -Embedded Server ---------------- - -Instead of connecting to a stand-alone server over the network, -the embedded server support lets you run a full server right in -your Python code or application server. - -If you have built MySQLdb with embedded server support, there -are two additional functions you will need to make use of: - - server_init(args, groups) - Initialize embedded server. If this client is not linked against - the embedded server library, this function does nothing. - - args - sequence of command-line arguments - groups - sequence of groups to use in defaults files - - server_end() - Shut down embedded server. If not using an embedded server, this - does nothing. - -See the MySQL documentation for more information on the embedded -server. - - :Title: MySQLdb: a Python interface for MySQL :Author: Andy Dustman diff --git a/setup_posix.py b/setup_posix.py index aa4b260b..7b86ec73 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -2,7 +2,6 @@ try: from ConfigParser import SafeConfigParser except ImportError: - # Probably running Python 3.x from configparser import ConfigParser as SafeConfigParser # This dequote() business is required for some older versions @@ -15,27 +14,29 @@ def dequote(s): s = s[1:-1] return s +_mysql_config_path = "mysql_config" + def mysql_config(what): from os import popen - f = popen("%s --%s" % (mysql_config.path, what)) + f = popen("%s --%s" % (_mysql_config_path, what)) data = f.read().strip().split() ret = f.close() if ret: if ret/256: data = [] if ret/256 > 1: - raise EnvironmentError("%s not found" % (mysql_config.path,)) + raise EnvironmentError("%s not found" % (_mysql_config_path,)) return data -mysql_config.path = "mysql_config" def get_config(): from setup_common import get_metadata_and_options, enabled, create_release_file + global _mysql_config_path metadata, options = get_metadata_and_options() if 'mysql_config' in options: - mysql_config.path = options['mysql_config'] + _mysql_config_path = options['mysql_config'] extra_objects = [] static = enabled(options, 'static') @@ -47,15 +48,7 @@ def get_config(): static = True sys.argv.remove('--static') - if enabled(options, 'embedded'): - libs = mysql_config("libmysqld-libs") - elif enabled(options, 'threadsafe'): - libs = mysql_config("libs_r") - if not libs: - libs = mysql_config("libs") - else: - libs = mysql_config("libs") - + libs = mysql_config("libs") library_dirs = [dequote(i[2:]) for i in libs if i.startswith('-L')] libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')] extra_link_args = [x for x in libs if not x.startswith(('-l', '-L'))] @@ -92,8 +85,6 @@ def get_config(): libraries.remove(client) name = "mysqlclient" - if enabled(options, 'embedded'): - name = name + "-embedded" metadata['name'] = name define_macros = [ diff --git a/site.cfg b/site.cfg index 8e2ab991..6b4596a4 100644 --- a/site.cfg +++ b/site.cfg @@ -1,10 +1,5 @@ [options] -# embedded: link against the embedded server library -# threadsafe: use the threadsafe client -# static: link against a static library (probably required for embedded) - -embedded = False -threadsafe = True +# static: link against a static library static = False # The path to mysql_config. From 67903f719fb29494491e1b3bbda25d6f359de043 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 19:18:14 +0900 Subject: [PATCH 122/396] Replace TAB with 4 SPACEs --- _mysql.c | 3574 +++++++++++++++++++++++++++--------------------------- 1 file changed, 1787 insertions(+), 1787 deletions(-) diff --git a/_mysql.c b/_mysql.c index 430b149b..537e61bc 100644 --- a/_mysql.c +++ b/_mysql.c @@ -54,24 +54,24 @@ static PyObject *_mysql_MySQLError; static PyObject *_mysql_Warning; static PyObject *_mysql_Error; static PyObject *_mysql_DatabaseError; -static PyObject *_mysql_InterfaceError; +static PyObject *_mysql_InterfaceError; static PyObject *_mysql_DataError; -static PyObject *_mysql_OperationalError; -static PyObject *_mysql_IntegrityError; -static PyObject *_mysql_InternalError; +static PyObject *_mysql_OperationalError; +static PyObject *_mysql_IntegrityError; +static PyObject *_mysql_InternalError; static PyObject *_mysql_ProgrammingError; static PyObject *_mysql_NotSupportedError; typedef struct { - PyObject_HEAD - MYSQL connection; - int open; - PyObject *converter; + PyObject_HEAD + MYSQL connection; + int open; + PyObject *converter; } _mysql_ConnectionObject; #define check_connection(c) \ if (!(c->open)) { \ - return _mysql_Exception(c); \ + return _mysql_Exception(c); \ }; #define result_connection(r) ((_mysql_ConnectionObject *)r->conn) @@ -80,13 +80,13 @@ typedef struct { extern PyTypeObject _mysql_ConnectionObject_Type; typedef struct { - PyObject_HEAD - PyObject *conn; - MYSQL_RES *result; - int nfields; - int use; - char has_next; - PyObject *converter; + PyObject_HEAD + PyObject *conn; + MYSQL_RES *result; + int nfields; + int use; + char has_next; + PyObject *converter; } _mysql_ResultObject; extern PyTypeObject _mysql_ResultObject_Type; @@ -101,122 +101,122 @@ extern PyTypeObject _mysql_ResultObject_Type; PyObject * _mysql_Exception(_mysql_ConnectionObject *c) { - PyObject *t, *e; - int merr; - - if (!(t = PyTuple_New(2))) return NULL; - if (!(c->open)) { - /* GH-270: When connection is closed, accessing the c->connection - * object may cause SEGV. - */ - merr = CR_SERVER_GONE_ERROR; - } - else { - merr = mysql_errno(&(c->connection)); - } - switch (merr) { - case 0: - e = _mysql_InterfaceError; - break; - case CR_COMMANDS_OUT_OF_SYNC: - case ER_DB_CREATE_EXISTS: - case ER_SYNTAX_ERROR: - case ER_PARSE_ERROR: - case ER_NO_SUCH_TABLE: - case ER_WRONG_DB_NAME: - case ER_WRONG_TABLE_NAME: - case ER_FIELD_SPECIFIED_TWICE: - case ER_INVALID_GROUP_FUNC_USE: - case ER_UNSUPPORTED_EXTENSION: - case ER_TABLE_MUST_HAVE_COLUMNS: + PyObject *t, *e; + int merr; + + if (!(t = PyTuple_New(2))) return NULL; + if (!(c->open)) { + /* GH-270: When connection is closed, accessing the c->connection + * object may cause SEGV. + */ + merr = CR_SERVER_GONE_ERROR; + } + else { + merr = mysql_errno(&(c->connection)); + } + switch (merr) { + case 0: + e = _mysql_InterfaceError; + break; + case CR_COMMANDS_OUT_OF_SYNC: + case ER_DB_CREATE_EXISTS: + case ER_SYNTAX_ERROR: + case ER_PARSE_ERROR: + case ER_NO_SUCH_TABLE: + case ER_WRONG_DB_NAME: + case ER_WRONG_TABLE_NAME: + case ER_FIELD_SPECIFIED_TWICE: + case ER_INVALID_GROUP_FUNC_USE: + case ER_UNSUPPORTED_EXTENSION: + case ER_TABLE_MUST_HAVE_COLUMNS: #ifdef ER_CANT_DO_THIS_DURING_AN_TRANSACTION - case ER_CANT_DO_THIS_DURING_AN_TRANSACTION: + case ER_CANT_DO_THIS_DURING_AN_TRANSACTION: #endif - e = _mysql_ProgrammingError; - break; + e = _mysql_ProgrammingError; + break; #ifdef WARN_DATA_TRUNCATED - case WARN_DATA_TRUNCATED: + case WARN_DATA_TRUNCATED: #ifdef WARN_NULL_TO_NOTNULL - case WARN_NULL_TO_NOTNULL: + case WARN_NULL_TO_NOTNULL: #endif #ifdef ER_WARN_DATA_OUT_OF_RANGE - case ER_WARN_DATA_OUT_OF_RANGE: + case ER_WARN_DATA_OUT_OF_RANGE: #endif #ifdef ER_NO_DEFAULT - case ER_NO_DEFAULT: + case ER_NO_DEFAULT: #endif #ifdef ER_PRIMARY_CANT_HAVE_NULL - case ER_PRIMARY_CANT_HAVE_NULL: + case ER_PRIMARY_CANT_HAVE_NULL: #endif #ifdef ER_DATA_TOO_LONG - case ER_DATA_TOO_LONG: + case ER_DATA_TOO_LONG: #endif #ifdef ER_DATETIME_FUNCTION_OVERFLOW - case ER_DATETIME_FUNCTION_OVERFLOW: + case ER_DATETIME_FUNCTION_OVERFLOW: #endif - e = _mysql_DataError; - break; + e = _mysql_DataError; + break; #endif - case ER_DUP_ENTRY: + case ER_DUP_ENTRY: #ifdef ER_DUP_UNIQUE - case ER_DUP_UNIQUE: + case ER_DUP_UNIQUE: #endif #ifdef ER_NO_REFERENCED_ROW - case ER_NO_REFERENCED_ROW: + case ER_NO_REFERENCED_ROW: #endif #ifdef ER_NO_REFERENCED_ROW_2 - case ER_NO_REFERENCED_ROW_2: + case ER_NO_REFERENCED_ROW_2: #endif #ifdef ER_ROW_IS_REFERENCED - case ER_ROW_IS_REFERENCED: + case ER_ROW_IS_REFERENCED: #endif #ifdef ER_ROW_IS_REFERENCED_2 - case ER_ROW_IS_REFERENCED_2: + case ER_ROW_IS_REFERENCED_2: #endif #ifdef ER_CANNOT_ADD_FOREIGN - case ER_CANNOT_ADD_FOREIGN: + case ER_CANNOT_ADD_FOREIGN: #endif #ifdef ER_NO_DEFAULT_FOR_FIELD - case ER_NO_DEFAULT_FOR_FIELD: + case ER_NO_DEFAULT_FOR_FIELD: #endif - e = _mysql_IntegrityError; - break; + e = _mysql_IntegrityError; + break; #ifdef ER_WARNING_NOT_COMPLETE_ROLLBACK - case ER_WARNING_NOT_COMPLETE_ROLLBACK: + case ER_WARNING_NOT_COMPLETE_ROLLBACK: #endif #ifdef ER_NOT_SUPPORTED_YET - case ER_NOT_SUPPORTED_YET: + case ER_NOT_SUPPORTED_YET: #endif #ifdef ER_FEATURE_DISABLED - case ER_FEATURE_DISABLED: + case ER_FEATURE_DISABLED: #endif #ifdef ER_UNKNOWN_STORAGE_ENGINE - case ER_UNKNOWN_STORAGE_ENGINE: + case ER_UNKNOWN_STORAGE_ENGINE: #endif - e = _mysql_NotSupportedError; - break; - default: - if (merr < 1000) - e = _mysql_InternalError; - else - e = _mysql_OperationalError; - break; - } - PyTuple_SET_ITEM(t, 0, PyInt_FromLong((long)merr)); - PyTuple_SET_ITEM(t, 1, PyString_FromString(mysql_error(&(c->connection)))); - PyErr_SetObject(e, t); - Py_DECREF(t); - return NULL; + e = _mysql_NotSupportedError; + break; + default: + if (merr < 1000) + e = _mysql_InternalError; + else + e = _mysql_OperationalError; + break; + } + PyTuple_SET_ITEM(t, 0, PyInt_FromLong((long)merr)); + PyTuple_SET_ITEM(t, 1, PyString_FromString(mysql_error(&(c->connection)))); + PyErr_SetObject(e, t); + Py_DECREF(t); + return NULL; } static char _mysql_thread_safe__doc__[] = "Indicates whether the client is compiled as thread-safe."; static PyObject *_mysql_thread_safe( - PyObject *self, - PyObject *noargs) + PyObject *self, + PyObject *noargs) { - return PyInt_FromLong((long)mysql_thread_safe()); + return PyInt_FromLong((long)mysql_thread_safe()); } static char _mysql_ResultObject__doc__[] = @@ -230,197 +230,197 @@ Just forget you ever saw this. Forget... FOR-GET..."; static int _mysql_ResultObject_Initialize( - _mysql_ResultObject *self, - PyObject *args, - PyObject *kwargs) -{ - static char *kwlist[] = {"connection", "use", "converter", NULL}; - MYSQL_RES *result; - _mysql_ConnectionObject *conn=NULL; - int use=0; - PyObject *conv=NULL; - int n, i; - MYSQL_FIELD *fields; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|iO", kwlist, - &_mysql_ConnectionObject_Type, &conn, &use, &conv)) - return -1; - - self->conn = (PyObject *) conn; - Py_INCREF(conn); - self->use = use; - Py_BEGIN_ALLOW_THREADS ; - if (use) - result = mysql_use_result(&(conn->connection)); - else - result = mysql_store_result(&(conn->connection)); - self->result = result; - self->has_next = (char)mysql_more_results(&(conn->connection)); - Py_END_ALLOW_THREADS ; - if (!result) { - if (mysql_errno(&(conn->connection))) { - _mysql_Exception(conn); - return -1; - } - self->converter = PyTuple_New(0); - return 0; - } - n = mysql_num_fields(result); - self->nfields = n; - if (!(self->converter = PyTuple_New(n))) { - return -1; - } - fields = mysql_fetch_fields(result); - for (i=0; iconverter, i, fun); - } - - return 0; + _mysql_ResultObject *self, + PyObject *args, + PyObject *kwargs) +{ + static char *kwlist[] = {"connection", "use", "converter", NULL}; + MYSQL_RES *result; + _mysql_ConnectionObject *conn=NULL; + int use=0; + PyObject *conv=NULL; + int n, i; + MYSQL_FIELD *fields; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|iO", kwlist, + &_mysql_ConnectionObject_Type, &conn, &use, &conv)) + return -1; + + self->conn = (PyObject *) conn; + Py_INCREF(conn); + self->use = use; + Py_BEGIN_ALLOW_THREADS ; + if (use) + result = mysql_use_result(&(conn->connection)); + else + result = mysql_store_result(&(conn->connection)); + self->result = result; + self->has_next = (char)mysql_more_results(&(conn->connection)); + Py_END_ALLOW_THREADS ; + if (!result) { + if (mysql_errno(&(conn->connection))) { + _mysql_Exception(conn); + return -1; + } + self->converter = PyTuple_New(0); + return 0; + } + n = mysql_num_fields(result); + self->nfields = n; + if (!(self->converter = PyTuple_New(n))) { + return -1; + } + fields = mysql_fetch_fields(result); + for (i=0; iconverter, i, fun); + } + + return 0; } static int _mysql_ResultObject_traverse( - _mysql_ResultObject *self, - visitproc visit, - void *arg) + _mysql_ResultObject *self, + visitproc visit, + void *arg) { - int r; - if (self->converter) { - if (!(r = visit(self->converter, arg))) return r; - } - if (self->conn) - return visit(self->conn, arg); - return 0; + int r; + if (self->converter) { + if (!(r = visit(self->converter, arg))) return r; + } + if (self->conn) + return visit(self->conn, arg); + return 0; } static int _mysql_ResultObject_clear(_mysql_ResultObject *self) { - Py_CLEAR(self->converter); - Py_CLEAR(self->conn); - return 0; + Py_CLEAR(self->converter); + Py_CLEAR(self->conn); + return 0; } static int _mysql_ConnectionObject_Initialize( - _mysql_ConnectionObject *self, - PyObject *args, - PyObject *kwargs) -{ - MYSQL *conn = NULL; - PyObject *conv = NULL; - PyObject *ssl = NULL; - char *key = NULL, *cert = NULL, *ca = NULL, - *capath = NULL, *cipher = NULL; - PyObject *ssl_keepref[5] = {NULL}; - int n_ssl_keepref = 0; - char *host = NULL, *user = NULL, *passwd = NULL, - *db = NULL, *unix_socket = NULL; - unsigned int port = 0; - unsigned int client_flag = 0; - static char *kwlist[] = { "host", "user", "passwd", "db", "port", - "unix_socket", "conv", - "connect_timeout", "compress", - "named_pipe", "init_command", - "read_default_file", "read_default_group", - "client_flag", "ssl", - "local_infile", + _mysql_ConnectionObject *self, + PyObject *args, + PyObject *kwargs) +{ + MYSQL *conn = NULL; + PyObject *conv = NULL; + PyObject *ssl = NULL; + char *key = NULL, *cert = NULL, *ca = NULL, + *capath = NULL, *cipher = NULL; + PyObject *ssl_keepref[5] = {NULL}; + int n_ssl_keepref = 0; + char *host = NULL, *user = NULL, *passwd = NULL, + *db = NULL, *unix_socket = NULL; + unsigned int port = 0; + unsigned int client_flag = 0; + static char *kwlist[] = { "host", "user", "passwd", "db", "port", + "unix_socket", "conv", + "connect_timeout", "compress", + "named_pipe", "init_command", + "read_default_file", "read_default_group", + "client_flag", "ssl", + "local_infile", #ifdef HAVE_MYSQL_OPT_TIMEOUTS - "read_timeout", - "write_timeout", + "read_timeout", + "write_timeout", #endif - NULL } ; - int connect_timeout = 0; + NULL } ; + int connect_timeout = 0; #ifdef HAVE_MYSQL_OPT_TIMEOUTS - int read_timeout = 0; - int write_timeout = 0; + int read_timeout = 0; + int write_timeout = 0; #endif - int compress = -1, named_pipe = -1, local_infile = -1; - char *init_command=NULL, - *read_default_file=NULL, - *read_default_group=NULL; + int compress = -1, named_pipe = -1, local_infile = -1; + char *init_command=NULL, + *read_default_file=NULL, + *read_default_group=NULL; - self->converter = NULL; - self->open = 0; + self->converter = NULL; + self->open = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, #ifdef HAVE_MYSQL_OPT_TIMEOUTS - "|ssssisOiiisssiOiii:connect", + "|ssssisOiiisssiOiii:connect", #else - "|ssssisOiiisssiOi:connect", + "|ssssisOiiisssiOi:connect", #endif - kwlist, - &host, &user, &passwd, &db, - &port, &unix_socket, &conv, - &connect_timeout, - &compress, &named_pipe, - &init_command, &read_default_file, - &read_default_group, - &client_flag, &ssl, - &local_infile + kwlist, + &host, &user, &passwd, &db, + &port, &unix_socket, &conv, + &connect_timeout, + &compress, &named_pipe, + &init_command, &read_default_file, + &read_default_group, + &client_flag, &ssl, + &local_infile #ifdef HAVE_MYSQL_OPT_TIMEOUTS - , &read_timeout - , &write_timeout + , &read_timeout + , &write_timeout #endif - )) - return -1; + )) + return -1; #ifdef IS_PY3K #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ @@ -432,90 +432,90 @@ _mysql_ConnectionObject_Initialize( PyErr_Clear();} #endif - if (ssl) { - PyObject *value = NULL; - _stringsuck(ca, value, ssl); - _stringsuck(capath, value, ssl); - _stringsuck(cert, value, ssl); - _stringsuck(key, value, ssl); - _stringsuck(cipher, value, ssl); - } - - Py_BEGIN_ALLOW_THREADS ; - conn = mysql_init(&(self->connection)); - if (connect_timeout) { - unsigned int timeout = connect_timeout; - mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, - (char *)&timeout); - } + if (ssl) { + PyObject *value = NULL; + _stringsuck(ca, value, ssl); + _stringsuck(capath, value, ssl); + _stringsuck(cert, value, ssl); + _stringsuck(key, value, ssl); + _stringsuck(cipher, value, ssl); + } + + Py_BEGIN_ALLOW_THREADS ; + conn = mysql_init(&(self->connection)); + if (connect_timeout) { + unsigned int timeout = connect_timeout; + mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, + (char *)&timeout); + } #ifdef HAVE_MYSQL_OPT_TIMEOUTS - if (read_timeout) { - unsigned int timeout = read_timeout; - mysql_options(&(self->connection), MYSQL_OPT_READ_TIMEOUT, - (char *)&timeout); - } - if (write_timeout) { - unsigned int timeout = write_timeout; - mysql_options(&(self->connection), MYSQL_OPT_WRITE_TIMEOUT, - (char *)&timeout); - } + if (read_timeout) { + unsigned int timeout = read_timeout; + mysql_options(&(self->connection), MYSQL_OPT_READ_TIMEOUT, + (char *)&timeout); + } + if (write_timeout) { + unsigned int timeout = write_timeout; + mysql_options(&(self->connection), MYSQL_OPT_WRITE_TIMEOUT, + (char *)&timeout); + } #endif - if (compress != -1) { - mysql_options(&(self->connection), MYSQL_OPT_COMPRESS, 0); - client_flag |= CLIENT_COMPRESS; - } - if (named_pipe != -1) - mysql_options(&(self->connection), MYSQL_OPT_NAMED_PIPE, 0); - if (init_command != NULL) - mysql_options(&(self->connection), MYSQL_INIT_COMMAND, init_command); - if (read_default_file != NULL) - mysql_options(&(self->connection), MYSQL_READ_DEFAULT_FILE, read_default_file); - if (read_default_group != NULL) - mysql_options(&(self->connection), MYSQL_READ_DEFAULT_GROUP, read_default_group); - - if (local_infile != -1) - mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); - - if (ssl) { - mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); - } - - conn = mysql_real_connect(&(self->connection), host, user, passwd, db, - port, unix_socket, client_flag); - - Py_END_ALLOW_THREADS ; - - if (ssl) { - int i; - for (i=0; iconverter = conv; - - /* - PyType_GenericAlloc() automatically sets up GC allocation and - tracking for GC objects, at least in 2.2.1, so it does not need to - be done here. tp_dealloc still needs to call PyObject_GC_UnTrack(), - however. - */ - self->open = 1; - return 0; + if (compress != -1) { + mysql_options(&(self->connection), MYSQL_OPT_COMPRESS, 0); + client_flag |= CLIENT_COMPRESS; + } + if (named_pipe != -1) + mysql_options(&(self->connection), MYSQL_OPT_NAMED_PIPE, 0); + if (init_command != NULL) + mysql_options(&(self->connection), MYSQL_INIT_COMMAND, init_command); + if (read_default_file != NULL) + mysql_options(&(self->connection), MYSQL_READ_DEFAULT_FILE, read_default_file); + if (read_default_group != NULL) + mysql_options(&(self->connection), MYSQL_READ_DEFAULT_GROUP, read_default_group); + + if (local_infile != -1) + mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); + + if (ssl) { + mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); + } + + conn = mysql_real_connect(&(self->connection), host, user, passwd, db, + port, unix_socket, client_flag); + + Py_END_ALLOW_THREADS ; + + if (ssl) { + int i; + for (i=0; iconverter = conv; + + /* + PyType_GenericAlloc() automatically sets up GC allocation and + tracking for GC objects, at least in 2.2.1, so it does not need to + be done here. tp_dealloc still needs to call PyObject_GC_UnTrack(), + however. + */ + self->open = 1; + return 0; } static char _mysql_connect__doc__[] = @@ -574,37 +574,37 @@ load_infile\n\ static PyObject * _mysql_connect( - PyObject *self, - PyObject *args, - PyObject *kwargs) + PyObject *self, + PyObject *args, + PyObject *kwargs) { - _mysql_ConnectionObject *c=NULL; + _mysql_ConnectionObject *c=NULL; - c = MyAlloc(_mysql_ConnectionObject, _mysql_ConnectionObject_Type); - if (c == NULL) return NULL; - if (_mysql_ConnectionObject_Initialize(c, args, kwargs)) { - Py_DECREF(c); - c = NULL; - } - return (PyObject *) c; + c = MyAlloc(_mysql_ConnectionObject, _mysql_ConnectionObject_Type); + if (c == NULL) return NULL; + if (_mysql_ConnectionObject_Initialize(c, args, kwargs)) { + Py_DECREF(c); + c = NULL; + } + return (PyObject *) c; } static int _mysql_ConnectionObject_traverse( - _mysql_ConnectionObject *self, - visitproc visit, - void *arg) + _mysql_ConnectionObject *self, + visitproc visit, + void *arg) { - if (self->converter) - return visit(self->converter, arg); - return 0; + if (self->converter) + return visit(self->converter, arg); + return 0; } static int _mysql_ConnectionObject_clear( - _mysql_ConnectionObject *self) + _mysql_ConnectionObject *self) { - Py_XDECREF(self->converter); - self->converter = NULL; - return 0; + Py_XDECREF(self->converter); + self->converter = NULL; + return 0; } static char _mysql_ConnectionObject_fileno__doc__[] = @@ -612,10 +612,10 @@ static char _mysql_ConnectionObject_fileno__doc__[] = static PyObject * _mysql_ConnectionObject_fileno( - _mysql_ConnectionObject *self) + _mysql_ConnectionObject *self) { check_connection(self); - return PyInt_FromLong(self->connection.net.fd); + return PyInt_FromLong(self->connection.net.fd); } static char _mysql_ConnectionObject_close__doc__[] = @@ -623,16 +623,16 @@ static char _mysql_ConnectionObject_close__doc__[] = static PyObject * _mysql_ConnectionObject_close( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { check_connection(self); Py_BEGIN_ALLOW_THREADS mysql_close(&(self->connection)); Py_END_ALLOW_THREADS self->open = 0; - _mysql_ConnectionObject_clear(self); - Py_RETURN_NONE; + _mysql_ConnectionObject_clear(self); + Py_RETURN_NONE; } static char _mysql_ConnectionObject_affected_rows__doc__ [] = @@ -642,15 +642,15 @@ Non-standard. Use Cursor.rowcount.\n\ static PyObject * _mysql_ConnectionObject_affected_rows( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - my_ulonglong ret; - check_connection(self); - ret = mysql_affected_rows(&(self->connection)); - if (ret == (my_ulonglong)-1) - return PyInt_FromLong(-1); - return PyLong_FromUnsignedLongLong(ret); + my_ulonglong ret; + check_connection(self); + ret = mysql_affected_rows(&(self->connection)); + if (ret == (my_ulonglong)-1) + return PyInt_FromLong(-1); + return PyLong_FromUnsignedLongLong(ret); } static char _mysql_debug__doc__[] = @@ -661,14 +661,14 @@ support debugging.\n\ "; static PyObject * _mysql_debug( - PyObject *self, - PyObject *args) + PyObject *self, + PyObject *args) { - char *debug; - if (!PyArg_ParseTuple(args, "s", &debug)) return NULL; - mysql_debug(debug); - Py_INCREF(Py_None); - return Py_None; + char *debug; + if (!PyArg_ParseTuple(args, "s", &debug)) return NULL; + mysql_debug(debug); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_dump_debug_info__doc__[] = @@ -679,17 +679,17 @@ this to work. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_dump_debug_info( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - int err; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_dump_debug_info(&(self->connection)); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_dump_debug_info(&(self->connection)); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_autocommit__doc__[] = @@ -697,18 +697,18 @@ static char _mysql_ConnectionObject_autocommit__doc__[] = "; static PyObject * _mysql_ConnectionObject_autocommit( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - int flag, err; - if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_autocommit(&(self->connection), flag); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + int flag, err; + if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_autocommit(&(self->connection), flag); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_get_autocommit__doc__[] = @@ -716,14 +716,14 @@ static char _mysql_ConnectionObject_get_autocommit__doc__[] = static PyObject * _mysql_ConnectionObject_get_autocommit( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - check_connection(self); - if (self->connection.server_status & SERVER_STATUS_AUTOCOMMIT) { - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; + check_connection(self); + if (self->connection.server_status & SERVER_STATUS_AUTOCOMMIT) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; } static char _mysql_ConnectionObject_commit__doc__[] = @@ -731,17 +731,17 @@ static char _mysql_ConnectionObject_commit__doc__[] = "; static PyObject * _mysql_ConnectionObject_commit( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - int err; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_commit(&(self->connection)); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_commit(&(self->connection)); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_rollback__doc__[] = @@ -749,15 +749,15 @@ static char _mysql_ConnectionObject_rollback__doc__[] = "; static PyObject * _mysql_ConnectionObject_rollback( - _mysql_ConnectionObject *self, - PyObject *noargs) -{ - int err; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_rollback(&(self->connection)); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_rollback(&(self->connection)); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); Py_RETURN_NONE; } @@ -776,17 +776,17 @@ Non-standard.\n\ "; static PyObject * _mysql_ConnectionObject_next_result( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - int err; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_next_result(&(self->connection)); - Py_END_ALLOW_THREADS - if (err > 0) return _mysql_Exception(self); - return PyInt_FromLong(err); -} + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_next_result(&(self->connection)); + Py_END_ALLOW_THREADS + if (err > 0) return _mysql_Exception(self); + return PyInt_FromLong(err); +} static char _mysql_ConnectionObject_set_server_option__doc__[] = @@ -797,19 +797,19 @@ Non-standard.\n\ "; static PyObject * _mysql_ConnectionObject_set_server_option( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int err, flags=0; - if (!PyArg_ParseTuple(args, "i", &flags)) - return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_set_server_option(&(self->connection), flags); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - return PyInt_FromLong(err); -} + _mysql_ConnectionObject *self, + PyObject *args) +{ + int err, flags=0; + if (!PyArg_ParseTuple(args, "i", &flags)) + return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_set_server_option(&(self->connection), flags); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + return PyInt_FromLong(err); +} static char _mysql_ConnectionObject_sqlstate__doc__[] = "Returns a string containing the SQLSTATE error code\n\ @@ -825,11 +825,11 @@ Non-standard.\n\ "; static PyObject * _mysql_ConnectionObject_sqlstate( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyString_FromString(mysql_sqlstate(&(self->connection))); + check_connection(self); + return PyString_FromString(mysql_sqlstate(&(self->connection))); } static char _mysql_ConnectionObject_warning_count__doc__[] = @@ -840,12 +840,12 @@ Non-standard.\n\ "; static PyObject * _mysql_ConnectionObject_warning_count( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyInt_FromLong(mysql_warning_count(&(self->connection))); -} + check_connection(self); + return PyInt_FromLong(mysql_warning_count(&(self->connection))); +} static char _mysql_ConnectionObject_errno__doc__[] = "Returns the error code for the most recently invoked API function\n\ @@ -855,11 +855,11 @@ occurred.\n\ static PyObject * _mysql_ConnectionObject_errno( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyInt_FromLong((long)mysql_errno(&(self->connection))); + check_connection(self); + return PyInt_FromLong((long)mysql_errno(&(self->connection))); } static char _mysql_ConnectionObject_error__doc__[] = @@ -870,11 +870,11 @@ occurred.\n\ static PyObject * _mysql_ConnectionObject_error( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyString_FromString(mysql_error(&(self->connection))); + check_connection(self); + return PyString_FromString(mysql_error(&(self->connection))); } static char _mysql_escape_string__doc__[] = @@ -887,30 +887,30 @@ it will escape entire sequences as well as strings."; static PyObject * _mysql_escape_string( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *str; - char *in, *out; - int len, size; - if (!PyArg_ParseTuple(args, "s#:escape_string", &in, &size)) return NULL; - str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); - if (!str) return PyErr_NoMemory(); - out = PyBytes_AS_STRING(str); - - if (self && PyModule_Check((PyObject*)self)) - self = NULL; - if (self && self->open) { + _mysql_ConnectionObject *self, + PyObject *args) +{ + PyObject *str; + char *in, *out; + int len, size; + if (!PyArg_ParseTuple(args, "s#:escape_string", &in, &size)) return NULL; + str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); + if (!str) return PyErr_NoMemory(); + out = PyBytes_AS_STRING(str); + + if (self && PyModule_Check((PyObject*)self)) + self = NULL; + if (self && self->open) { #if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) - len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); + len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); #else - len = mysql_real_escape_string(&(self->connection), out, in, size); + len = mysql_real_escape_string(&(self->connection), out, in, size); #endif - } else { - len = mysql_escape_string(out, in, size); - } - if (_PyBytes_Resize(&str, len) < 0) return NULL; - return (str); + } else { + len = mysql_escape_string(out, in, size); + } + if (_PyBytes_Resize(&str, len) < 0) return NULL; + return (str); } static char _mysql_string_literal__doc__[] = @@ -925,85 +925,85 @@ _mysql.string_literal(obj) cannot handle character sets."; static PyObject * _mysql_string_literal( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *str, *s, *o, *d; - char *in, *out; - int len, size; - if (self && PyModule_Check((PyObject*)self)) - self = NULL; - if (!PyArg_ParseTuple(args, "O|O:string_literal", &o, &d)) return NULL; - if (PyBytes_Check(o)) { - s = o; - Py_INCREF(s); - } else { - s = PyObject_Str(o); - if (!s) return NULL; + _mysql_ConnectionObject *self, + PyObject *args) +{ + PyObject *str, *s, *o, *d; + char *in, *out; + int len, size; + if (self && PyModule_Check((PyObject*)self)) + self = NULL; + if (!PyArg_ParseTuple(args, "O|O:string_literal", &o, &d)) return NULL; + if (PyBytes_Check(o)) { + s = o; + Py_INCREF(s); + } else { + s = PyObject_Str(o); + if (!s) return NULL; #ifdef IS_PY3K - { - PyObject *t = PyUnicode_AsASCIIString(s); - Py_DECREF(s); - if (!t) return NULL; - s = t; - } + { + PyObject *t = PyUnicode_AsASCIIString(s); + Py_DECREF(s); + if (!t) return NULL; + s = t; + } #endif - } - in = PyBytes_AsString(s); - size = PyBytes_GET_SIZE(s); - str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); - if (!str) { - Py_DECREF(s); - return PyErr_NoMemory(); - } - out = PyBytes_AS_STRING(str); - if (self && self->open) { + } + in = PyBytes_AsString(s); + size = PyBytes_GET_SIZE(s); + str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); + if (!str) { + Py_DECREF(s); + return PyErr_NoMemory(); + } + out = PyBytes_AS_STRING(str); + if (self && self->open) { #if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) - len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); + len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); #else - len = mysql_real_escape_string(&(self->connection), out+1, in, size); + len = mysql_real_escape_string(&(self->connection), out+1, in, size); #endif - } else { - len = mysql_escape_string(out+1, in, size); - } - *out = *(out+len+1) = '\''; - if (_PyBytes_Resize(&str, len+2) < 0) return NULL; - Py_DECREF(s); - return (str); + } else { + len = mysql_escape_string(out+1, in, size); + } + *out = *(out+len+1) = '\''; + if (_PyBytes_Resize(&str, len+2) < 0) return NULL; + Py_DECREF(s); + return (str); } static PyObject *_mysql_NULL; static PyObject * _escape_item( - PyObject *item, - PyObject *d) -{ - PyObject *quoted=NULL, *itemtype, *itemconv; - if (!(itemtype = PyObject_Type(item))) - goto error; - itemconv = PyObject_GetItem(d, itemtype); - Py_DECREF(itemtype); - if (!itemconv) { - PyErr_Clear(); - itemconv = PyObject_GetItem(d, + PyObject *item, + PyObject *d) +{ + PyObject *quoted=NULL, *itemtype, *itemconv; + if (!(itemtype = PyObject_Type(item))) + goto error; + itemconv = PyObject_GetItem(d, itemtype); + Py_DECREF(itemtype); + if (!itemconv) { + PyErr_Clear(); + itemconv = PyObject_GetItem(d, #ifdef IS_PY3K - (PyObject *) &PyUnicode_Type); + (PyObject *) &PyUnicode_Type); #else - (PyObject *) &PyString_Type); + (PyObject *) &PyString_Type); #endif - } - if (!itemconv) { - PyErr_SetString(PyExc_TypeError, - "no default type converter defined"); - goto error; - } - Py_INCREF(d); - quoted = PyObject_CallFunction(itemconv, "OO", item, d); - Py_DECREF(d); - Py_DECREF(itemconv); + } + if (!itemconv) { + PyErr_SetString(PyExc_TypeError, + "no default type converter defined"); + goto error; + } + Py_INCREF(d); + quoted = PyObject_CallFunction(itemconv, "OO", item, d); + Py_DECREF(d); + Py_DECREF(itemconv); error: - return quoted; + return quoted; } static char _mysql_escape__doc__[] = @@ -1012,28 +1012,28 @@ using mapping dict to provide quoting functions for each type.\n\ Returns a SQL literal string."; static PyObject * _mysql_escape( - PyObject *self, - PyObject *args) -{ - PyObject *o=NULL, *d=NULL; - if (!PyArg_ParseTuple(args, "O|O:escape", &o, &d)) - return NULL; - if (d) { - if (!PyMapping_Check(d)) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } - return _escape_item(o, d); - } else { - if (!self) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } - return _escape_item(o, - ((_mysql_ConnectionObject *) self)->converter); - } + PyObject *self, + PyObject *args) +{ + PyObject *o=NULL, *d=NULL; + if (!PyArg_ParseTuple(args, "O|O:escape", &o, &d)) + return NULL; + if (d) { + if (!PyMapping_Check(d)) { + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a mapping"); + return NULL; + } + return _escape_item(o, d); + } else { + if (!self) { + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a mapping"); + return NULL; + } + return _escape_item(o, + ((_mysql_ConnectionObject *) self)->converter); + } } static char _mysql_escape_sequence__doc__[] = @@ -1042,32 +1042,32 @@ seq using mapping dict to provide quoting functions for each type.\n\ Returns a tuple of escaped items."; static PyObject * _mysql_escape_sequence( - PyObject *self, - PyObject *args) -{ - PyObject *o=NULL, *d=NULL, *r=NULL, *item, *quoted; - int i, n; - if (!PyArg_ParseTuple(args, "OO:escape_sequence", &o, &d)) - goto error; - if (!PyMapping_Check(d)) { + PyObject *self, + PyObject *args) +{ + PyObject *o=NULL, *d=NULL, *r=NULL, *item, *quoted; + int i, n; + if (!PyArg_ParseTuple(args, "OO:escape_sequence", &o, &d)) + goto error; + if (!PyMapping_Check(d)) { PyErr_SetString(PyExc_TypeError, "argument 2 must be a mapping"); return NULL; } - if ((n = PyObject_Length(o)) == -1) goto error; - if (!(r = PyTuple_New(n))) goto error; - for (i=0; iresult); - fields = mysql_fetch_fields(self->result); - if (!(d = PyTuple_New(n))) return NULL; - for (i=0; iresult); + fields = mysql_fetch_fields(self->result); + if (!(d = PyTuple_New(n))) return NULL; + for (i=0; iresult); - fields = mysql_fetch_fields(self->result); - if (!(d = PyTuple_New(n))) return NULL; - for (i=0; iresult); + fields = mysql_fetch_fields(self->result); + if (!(d = PyTuple_New(n))) return NULL; + for (i=0; itype; - // Return bytes for binary and string types. - int binary = 0; - if (field_type == FIELD_TYPE_TINY_BLOB || - field_type == FIELD_TYPE_MEDIUM_BLOB || - field_type == FIELD_TYPE_LONG_BLOB || - field_type == FIELD_TYPE_BLOB || - field_type == FIELD_TYPE_VAR_STRING || - field_type == FIELD_TYPE_STRING || - field_type == FIELD_TYPE_GEOMETRY || - field_type == FIELD_TYPE_BIT) { - binary = 1; - } + int field_type = field->type; + // Return bytes for binary and string types. + int binary = 0; + if (field_type == FIELD_TYPE_TINY_BLOB || + field_type == FIELD_TYPE_MEDIUM_BLOB || + field_type == FIELD_TYPE_LONG_BLOB || + field_type == FIELD_TYPE_BLOB || + field_type == FIELD_TYPE_VAR_STRING || + field_type == FIELD_TYPE_STRING || + field_type == FIELD_TYPE_GEOMETRY || + field_type == FIELD_TYPE_BIT) { + binary = 1; + } #endif - if (rowitem) { - if (converter != Py_None) { - v = PyObject_CallFunction(converter, + if (rowitem) { + if (converter != Py_None) { + v = PyObject_CallFunction(converter, #ifdef IS_PY3K - binary ? "y#" : "s#", + binary ? "y#" : "s#", #else - "s#", + "s#", #endif - rowitem, - (int)length); - } else { + rowitem, + (int)length); + } else { #ifdef IS_PY3K - if (!binary) { - v = PyUnicode_FromStringAndSize(rowitem, (int)length); - } else + if (!binary) { + v = PyUnicode_FromStringAndSize(rowitem, (int)length); + } else #endif - v = PyBytes_FromStringAndSize(rowitem, (int)length); - } - if (!v) - return NULL; - } else { - Py_INCREF(Py_None); - v = Py_None; - } - return v; + v = PyBytes_FromStringAndSize(rowitem, (int)length); + } + if (!v) + return NULL; + } else { + Py_INCREF(Py_None); + v = Py_None; + } + return v; } static PyObject * _mysql_row_to_tuple( - _mysql_ResultObject *self, - MYSQL_ROW row) -{ - unsigned int n, i; - unsigned long *length; - PyObject *r, *c; - MYSQL_FIELD *fields; - - n = mysql_num_fields(self->result); - if (!(r = PyTuple_New(n))) return NULL; - length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); - for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); - if (!v) goto error; - PyTuple_SET_ITEM(r, i, v); - } - return r; + _mysql_ResultObject *self, + MYSQL_ROW row) +{ + unsigned int n, i; + unsigned long *length; + PyObject *r, *c; + MYSQL_FIELD *fields; + + n = mysql_num_fields(self->result); + if (!(r = PyTuple_New(n))) return NULL; + length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); + for (i=0; iconverter, i); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); + if (!v) goto error; + PyTuple_SET_ITEM(r, i, v); + } + return r; error: - Py_XDECREF(r); - return NULL; + Py_XDECREF(r); + return NULL; } static PyObject * _mysql_row_to_dict( - _mysql_ResultObject *self, - MYSQL_ROW row) -{ - unsigned int n, i; - unsigned long *length; - PyObject *r, *c; - MYSQL_FIELD *fields; - - n = mysql_num_fields(self->result); - if (!(r = PyDict_New())) return NULL; - length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); - for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); - if (!v) goto error; - if (!PyMapping_HasKeyString(r, fields[i].name)) { - PyMapping_SetItemString(r, fields[i].name, v); - } else { - int len; - char buf[256]; - strncpy(buf, fields[i].table, 256); - len = strlen(buf); - strncat(buf, ".", 256-len); - len = strlen(buf); - strncat(buf, fields[i].name, 256-len); - PyMapping_SetItemString(r, buf, v); - } - Py_DECREF(v); - } - return r; + _mysql_ResultObject *self, + MYSQL_ROW row) +{ + unsigned int n, i; + unsigned long *length; + PyObject *r, *c; + MYSQL_FIELD *fields; + + n = mysql_num_fields(self->result); + if (!(r = PyDict_New())) return NULL; + length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); + for (i=0; iconverter, i); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); + if (!v) goto error; + if (!PyMapping_HasKeyString(r, fields[i].name)) { + PyMapping_SetItemString(r, fields[i].name, v); + } else { + int len; + char buf[256]; + strncpy(buf, fields[i].table, 256); + len = strlen(buf); + strncat(buf, ".", 256-len); + len = strlen(buf); + strncat(buf, fields[i].name, 256-len); + PyMapping_SetItemString(r, buf, v); + } + Py_DECREF(v); + } + return r; error: - Py_XDECREF(r); - return NULL; + Py_XDECREF(r); + return NULL; } static PyObject * _mysql_row_to_dict_old( - _mysql_ResultObject *self, - MYSQL_ROW row) + _mysql_ResultObject *self, + MYSQL_ROW row) { - unsigned int n, i; - unsigned long *length; - PyObject *r, *c; + unsigned int n, i; + unsigned long *length; + PyObject *r, *c; MYSQL_FIELD *fields; - n = mysql_num_fields(self->result); - if (!(r = PyDict_New())) return NULL; - length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); - for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); - if (!v) goto error; - { - int len=0; - char buf[256]=""; - if (strlen(fields[i].table)) { - strncpy(buf, fields[i].table, 256); - len = strlen(buf); - strncat(buf, ".", 256-len); - len = strlen(buf); - } - strncat(buf, fields[i].name, 256-len); - PyMapping_SetItemString(r, buf, v); - } - Py_DECREF(v); - } - return r; + n = mysql_num_fields(self->result); + if (!(r = PyDict_New())) return NULL; + length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); + for (i=0; iconverter, i); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); + if (!v) goto error; + { + int len=0; + char buf[256]=""; + if (strlen(fields[i].table)) { + strncpy(buf, fields[i].table, 256); + len = strlen(buf); + strncat(buf, ".", 256-len); + len = strlen(buf); + } + strncat(buf, fields[i].name, 256-len); + PyMapping_SetItemString(r, buf, v); + } + Py_DECREF(v); + } + return r; error: - Py_XDECREF(r); - return NULL; + Py_XDECREF(r); + return NULL; } typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); int _mysql__fetch_row( - _mysql_ResultObject *self, - PyObject **r, - int skiprows, - int maxrows, - _PYFUNC *convert_row) -{ - int i; - MYSQL_ROW row; - - for (i = skiprows; i<(skiprows+maxrows); i++) { - PyObject *v; - if (!self->use) - row = mysql_fetch_row(self->result); - else { - Py_BEGIN_ALLOW_THREADS; - row = mysql_fetch_row(self->result); - Py_END_ALLOW_THREADS; - } - if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { - _mysql_Exception((_mysql_ConnectionObject *)self->conn); - goto error; - } - if (!row) { - if (_PyTuple_Resize(r, i) == -1) goto error; - break; - } - v = convert_row(self, row); - if (!v) goto error; - PyTuple_SET_ITEM(*r, i, v); - } - return i-skiprows; + _mysql_ResultObject *self, + PyObject **r, + int skiprows, + int maxrows, + _PYFUNC *convert_row) +{ + int i; + MYSQL_ROW row; + + for (i = skiprows; i<(skiprows+maxrows); i++) { + PyObject *v; + if (!self->use) + row = mysql_fetch_row(self->result); + else { + Py_BEGIN_ALLOW_THREADS; + row = mysql_fetch_row(self->result); + Py_END_ALLOW_THREADS; + } + if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { + _mysql_Exception((_mysql_ConnectionObject *)self->conn); + goto error; + } + if (!row) { + if (_PyTuple_Resize(r, i) == -1) goto error; + break; + } + v = convert_row(self, row); + if (!v) goto error; + PyTuple_SET_ITEM(*r, i, v); + } + return i-skiprows; error: - return -1; + return -1; } static char _mysql_ResultObject_fetch_row__doc__[] = @@ -1369,62 +1369,62 @@ The rows are formatted according to how:\n\ static PyObject * _mysql_ResultObject_fetch_row( - _mysql_ResultObject *self, - PyObject *args, - PyObject *kwargs) -{ - typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); - static char *kwlist[] = { "maxrows", "how", NULL }; - static _PYFUNC *row_converters[] = - { - _mysql_row_to_tuple, - _mysql_row_to_dict, - _mysql_row_to_dict_old - }; - _PYFUNC *convert_row; - int maxrows=1, how=0, skiprows=0, rowsadded; - PyObject *r=NULL; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, - &maxrows, &how)) - return NULL; - check_result_connection(self); - if (how >= (int)sizeof(row_converters)) { - PyErr_SetString(PyExc_ValueError, "how out of range"); - return NULL; - } - convert_row = row_converters[how]; - if (maxrows) { - if (!(r = PyTuple_New(maxrows))) goto error; - rowsadded = _mysql__fetch_row(self, &r, skiprows, maxrows, - convert_row); - if (rowsadded == -1) goto error; - } else { - if (self->use) { - maxrows = 1000; - if (!(r = PyTuple_New(maxrows))) goto error; - while (1) { - rowsadded = _mysql__fetch_row(self, &r, skiprows, - maxrows, convert_row); - if (rowsadded == -1) goto error; - skiprows += rowsadded; - if (rowsadded < maxrows) break; - if (_PyTuple_Resize(&r, skiprows+maxrows) == -1) - goto error; - } - } else { - /* XXX if overflow, maxrows<0? */ - maxrows = (int) mysql_num_rows(self->result); - if (!(r = PyTuple_New(maxrows))) goto error; - rowsadded = _mysql__fetch_row(self, &r, 0, - maxrows, convert_row); - if (rowsadded == -1) goto error; - } - } - return r; + _mysql_ResultObject *self, + PyObject *args, + PyObject *kwargs) +{ + typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); + static char *kwlist[] = { "maxrows", "how", NULL }; + static _PYFUNC *row_converters[] = + { + _mysql_row_to_tuple, + _mysql_row_to_dict, + _mysql_row_to_dict_old + }; + _PYFUNC *convert_row; + int maxrows=1, how=0, skiprows=0, rowsadded; + PyObject *r=NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, + &maxrows, &how)) + return NULL; + check_result_connection(self); + if (how >= (int)sizeof(row_converters)) { + PyErr_SetString(PyExc_ValueError, "how out of range"); + return NULL; + } + convert_row = row_converters[how]; + if (maxrows) { + if (!(r = PyTuple_New(maxrows))) goto error; + rowsadded = _mysql__fetch_row(self, &r, skiprows, maxrows, + convert_row); + if (rowsadded == -1) goto error; + } else { + if (self->use) { + maxrows = 1000; + if (!(r = PyTuple_New(maxrows))) goto error; + while (1) { + rowsadded = _mysql__fetch_row(self, &r, skiprows, + maxrows, convert_row); + if (rowsadded == -1) goto error; + skiprows += rowsadded; + if (rowsadded < maxrows) break; + if (_PyTuple_Resize(&r, skiprows+maxrows) == -1) + goto error; + } + } else { + /* XXX if overflow, maxrows<0? */ + maxrows = (int) mysql_num_rows(self->result); + if (!(r = PyTuple_New(maxrows))) goto error; + rowsadded = _mysql__fetch_row(self, &r, 0, + maxrows, convert_row); + if (rowsadded == -1) goto error; + } + } + return r; error: - Py_XDECREF(r); - return NULL; + Py_XDECREF(r); + return NULL; } static char _mysql_ConnectionObject_change_user__doc__[] = @@ -1446,24 +1446,24 @@ a default database.\n\ static PyObject * _mysql_ConnectionObject_change_user( - _mysql_ConnectionObject *self, - PyObject *args, - PyObject *kwargs) + _mysql_ConnectionObject *self, + PyObject *args, + PyObject *kwargs) { - char *user, *pwd=NULL, *db=NULL; - int r; + char *user, *pwd=NULL, *db=NULL; + int r; static char *kwlist[] = { "user", "passwd", "db", NULL } ; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", - kwlist, &user, &pwd, &db)) - return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_change_user(&(self->connection), user, pwd, db); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", + kwlist, &user, &pwd, &db)) + return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_change_user(&(self->connection), user, pwd, db); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_character_set_name__doc__[] = @@ -1473,13 +1473,13 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_character_set_name( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - const char *s; - check_connection(self); - s = mysql_character_set_name(&(self->connection)); - return PyString_FromString(s); + const char *s; + check_connection(self); + s = mysql_character_set_name(&(self->connection)); + return PyString_FromString(s); } static char _mysql_ConnectionObject_set_character_set__doc__[] = @@ -1489,19 +1489,19 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_set_character_set( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - const char *s; - int err; - if (!PyArg_ParseTuple(args, "s", &s)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_set_character_set(&(self->connection), s); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + const char *s; + int err; + if (!PyArg_ParseTuple(args, "s", &s)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_set_character_set(&(self->connection), s); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } #if MYSQL_VERSION_ID >= 50010 @@ -1528,26 +1528,26 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_character_set_info( - _mysql_ConnectionObject *self, - PyObject *noargs) -{ - PyObject *result; - MY_CHARSET_INFO cs; - - check_connection(self); - mysql_get_character_set_info(&(self->connection), &cs); - if (!(result = PyDict_New())) return NULL; - if (cs.csname) - PyDict_SetItemString(result, "name", PyString_FromString(cs.csname)); - if (cs.name) - PyDict_SetItemString(result, "collation", PyString_FromString(cs.name)); - if (cs.comment) - PyDict_SetItemString(result, "comment", PyString_FromString(cs.comment)); - if (cs.dir) - PyDict_SetItemString(result, "dir", PyString_FromString(cs.dir)); - PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); - PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); - return result; + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *result; + MY_CHARSET_INFO cs; + + check_connection(self); + mysql_get_character_set_info(&(self->connection), &cs); + if (!(result = PyDict_New())) return NULL; + if (cs.csname) + PyDict_SetItemString(result, "name", PyString_FromString(cs.csname)); + if (cs.name) + PyDict_SetItemString(result, "collation", PyString_FromString(cs.name)); + if (cs.comment) + PyDict_SetItemString(result, "comment", PyString_FromString(cs.comment)); + if (cs.dir) + PyDict_SetItemString(result, "dir", PyString_FromString(cs.dir)); + PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); + PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); + return result; } #endif @@ -1562,14 +1562,14 @@ NOTE: this is a private API introduced ONLY for XTA integration,\n\ static PyObject * _mysql_ConnectionObject_get_native_connection( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - PyObject *result; - check_connection(self); - result = PyCapsule_New(&(self->connection), - "_mysql.connection.native_connection", NULL); - return result; + PyObject *result; + check_connection(self); + result = PyCapsule_New(&(self->connection), + "_mysql.connection.native_connection", NULL); + return result; } @@ -1578,10 +1578,10 @@ static char _mysql_get_client_info__doc__[] = the client library version."; static PyObject * _mysql_get_client_info( - PyObject *self, - PyObject *noargs) + PyObject *self, + PyObject *noargs) { - return PyString_FromString(mysql_get_client_info()); + return PyString_FromString(mysql_get_client_info()); } static char _mysql_ConnectionObject_get_host_info__doc__[] = @@ -1591,11 +1591,11 @@ version. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_host_info( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyString_FromString(mysql_get_host_info(&(self->connection))); + check_connection(self); + return PyString_FromString(mysql_get_host_info(&(self->connection))); } static char _mysql_ConnectionObject_get_proto_info__doc__[] = @@ -1605,11 +1605,11 @@ used by the current connection. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_proto_info( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); + check_connection(self); + return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); } static char _mysql_ConnectionObject_get_server_info__doc__[] = @@ -1619,11 +1619,11 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_get_server_info( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyString_FromString(mysql_get_server_info(&(self->connection))); + check_connection(self); + return PyString_FromString(mysql_get_server_info(&(self->connection))); } static char _mysql_ConnectionObject_info__doc__[] = @@ -1634,15 +1634,15 @@ Cursor.messages.\n\ static PyObject * _mysql_ConnectionObject_info( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - const char *s; - check_connection(self); - s = mysql_info(&(self->connection)); - if (s) return PyString_FromString(s); - Py_INCREF(Py_None); - return Py_None; + const char *s; + check_connection(self); + s = mysql_info(&(self->connection)); + if (s) return PyString_FromString(s); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_insert_id__doc__[] = @@ -1668,15 +1668,15 @@ in the server.\n\ static PyObject * _mysql_ConnectionObject_insert_id( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - my_ulonglong r; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_insert_id(&(self->connection)); - Py_END_ALLOW_THREADS - return PyLong_FromUnsignedLongLong(r); + my_ulonglong r; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_insert_id(&(self->connection)); + Py_END_ALLOW_THREADS + return PyLong_FromUnsignedLongLong(r); } static char _mysql_ConnectionObject_kill__doc__[] = @@ -1685,19 +1685,19 @@ Non-standard."; static PyObject * _mysql_ConnectionObject_kill( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - unsigned long pid; - int r; - if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_kill(&(self->connection), pid); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + unsigned long pid; + int r; + if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_kill(&(self->connection), pid); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_field_count__doc__[] = @@ -1708,24 +1708,24 @@ on most cursor classes. Use Cursor.rowcount.\n\ static PyObject * _mysql_ConnectionObject_field_count( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - check_connection(self); - return PyInt_FromLong((long)mysql_field_count(&(self->connection))); -} + check_connection(self); + return PyInt_FromLong((long)mysql_field_count(&(self->connection))); +} static char _mysql_ResultObject_num_fields__doc__[] = "Returns the number of fields (column) in the result." ; static PyObject * _mysql_ResultObject_num_fields( - _mysql_ResultObject *self, - PyObject *noargs) + _mysql_ResultObject *self, + PyObject *noargs) { - check_result_connection(self); - return PyInt_FromLong((long)mysql_num_fields(self->result)); -} + check_result_connection(self); + return PyInt_FromLong((long)mysql_num_fields(self->result)); +} static char _mysql_ResultObject_num_rows__doc__[] = "Returns the number of rows in the result set. Note that if\n\ @@ -1735,12 +1735,12 @@ set has been read.\n\ static PyObject * _mysql_ResultObject_num_rows( - _mysql_ResultObject *self, - PyObject *noargs) + _mysql_ResultObject *self, + PyObject *noargs) { - check_result_connection(self); - return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); -} + check_result_connection(self); + return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); +} static char _mysql_ConnectionObject_ping__doc__[] = "Checks whether or not the connection to the server is\n\ @@ -1763,22 +1763,22 @@ You have been warned.\n\ static PyObject * _mysql_ConnectionObject_ping( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int r, reconnect = -1; - if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; - check_connection(self); - if (reconnect != -1) { - my_bool recon = (my_bool)reconnect; - mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); - } - Py_BEGIN_ALLOW_THREADS - r = mysql_ping(&(self->connection)); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + _mysql_ConnectionObject *self, + PyObject *args) +{ + int r, reconnect = -1; + if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; + check_connection(self); + if (reconnect != -1) { + my_bool recon = (my_bool)reconnect; + mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); + } + Py_BEGIN_ALLOW_THREADS + r = mysql_ping(&(self->connection)); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_query__doc__[] = @@ -1789,20 +1789,20 @@ then cursor.execute().\n\ static PyObject * _mysql_ConnectionObject_query( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - char *query; - int len, r; - if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self); + char *query; + int len, r; + if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; + check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_real_query(&(self->connection), query, len); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_BEGIN_ALLOW_THREADS + r = mysql_real_query(&(self->connection), query, len); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } @@ -1812,21 +1812,21 @@ Use read_query_result() before calling store_result() or use_result()\n"; static PyObject * _mysql_ConnectionObject_send_query( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - char *query; - int len, r; - MYSQL *mysql = &(self->connection); - if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self); + char *query; + int len, r; + MYSQL *mysql = &(self->connection); + if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; + check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_send_query(mysql, query, len); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_BEGIN_ALLOW_THREADS + r = mysql_send_query(mysql, query, len); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } @@ -1835,19 +1835,19 @@ static char _mysql_ConnectionObject_read_query_result__doc__[] = static PyObject * _mysql_ConnectionObject_read_query_result( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - int r; - MYSQL *mysql = &(self->connection); - check_connection(self); + int r; + MYSQL *mysql = &(self->connection); + check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = (int)mysql_read_query_result(mysql); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_BEGIN_ALLOW_THREADS + r = (int)mysql_read_query_result(mysql); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_select_db__doc__[] = @@ -1864,19 +1864,19 @@ Non-standard.\n\ static PyObject * _mysql_ConnectionObject_select_db( - _mysql_ConnectionObject *self, - PyObject *args) + _mysql_ConnectionObject *self, + PyObject *args) { - char *db; - int r; - if (!PyArg_ParseTuple(args, "s:select_db", &db)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_select_db(&(self->connection), db); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + char *db; + int r; + if (!PyArg_ParseTuple(args, "s:select_db", &db)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_select_db(&(self->connection), db); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_shutdown__doc__[] = @@ -1886,17 +1886,17 @@ have shutdown privileges. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_shutdown( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - int r; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + int r; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; } static char _mysql_ConnectionObject_stat__doc__[] = @@ -1908,16 +1908,16 @@ questions, reloads, and open tables. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_stat( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - const char *s; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - s = mysql_stat(&(self->connection)); - Py_END_ALLOW_THREADS - if (!s) return _mysql_Exception(self); - return PyString_FromString(s); + const char *s; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + s = mysql_stat(&(self->connection)); + Py_END_ALLOW_THREADS + if (!s) return _mysql_Exception(self); + return PyString_FromString(s); } static char _mysql_ConnectionObject_store_result__doc__[] = @@ -1928,31 +1928,31 @@ None is returned. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_store_result( - _mysql_ConnectionObject *self, - PyObject *noargs) -{ - PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; - _mysql_ResultObject *r=NULL; - - check_connection(self); - arglist = Py_BuildValue("(OiO)", self, 0, self->converter); - if (!arglist) goto error; - kwarglist = PyDict_New(); - if (!kwarglist) goto error; - r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); - if (!r) goto error; - if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) - goto error; - result = (PyObject *) r; - if (!(r->result)) { - Py_DECREF(result); - Py_INCREF(Py_None); - result = Py_None; - } + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; + _mysql_ResultObject *r=NULL; + + check_connection(self); + arglist = Py_BuildValue("(OiO)", self, 0, self->converter); + if (!arglist) goto error; + kwarglist = PyDict_New(); + if (!kwarglist) goto error; + r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); + if (!r) goto error; + if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) + goto error; + result = (PyObject *) r; + if (!(r->result)) { + Py_DECREF(result); + Py_INCREF(Py_None); + result = Py_None; + } error: - Py_XDECREF(arglist); - Py_XDECREF(kwarglist); - return result; + Py_XDECREF(arglist); + Py_XDECREF(kwarglist); + return result; } static char _mysql_ConnectionObject_thread_id__doc__[] = @@ -1968,15 +1968,15 @@ Non-standard."; static PyObject * _mysql_ConnectionObject_thread_id( - _mysql_ConnectionObject *self, - PyObject *noargs) + _mysql_ConnectionObject *self, + PyObject *noargs) { - unsigned long pid; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - pid = mysql_thread_id(&(self->connection)); - Py_END_ALLOW_THREADS - return PyInt_FromLong((long)pid); + unsigned long pid; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + pid = mysql_thread_id(&(self->connection)); + Py_END_ALLOW_THREADS + return PyInt_FromLong((long)pid); } static char _mysql_ConnectionObject_use_result__doc__[] = @@ -1987,59 +1987,59 @@ None is returned. Non-standard.\n\ static PyObject * _mysql_ConnectionObject_use_result( - _mysql_ConnectionObject *self, - PyObject *noargs) -{ - PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; - _mysql_ResultObject *r=NULL; - - check_connection(self); - arglist = Py_BuildValue("(OiO)", self, 1, self->converter); - if (!arglist) return NULL; - kwarglist = PyDict_New(); - if (!kwarglist) goto error; - r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); - if (!r) goto error; - result = (PyObject *) r; - if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) - goto error; - if (!(r->result)) { - Py_DECREF(result); - Py_INCREF(Py_None); - result = Py_None; - } + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; + _mysql_ResultObject *r=NULL; + + check_connection(self); + arglist = Py_BuildValue("(OiO)", self, 1, self->converter); + if (!arglist) return NULL; + kwarglist = PyDict_New(); + if (!kwarglist) goto error; + r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); + if (!r) goto error; + result = (PyObject *) r; + if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) + goto error; + if (!(r->result)) { + Py_DECREF(result); + Py_INCREF(Py_None); + result = Py_None; + } error: - Py_DECREF(arglist); - Py_XDECREF(kwarglist); - return result; + Py_DECREF(arglist); + Py_XDECREF(kwarglist); + return result; } static void _mysql_ConnectionObject_dealloc( - _mysql_ConnectionObject *self) + _mysql_ConnectionObject *self) { - PyObject_GC_UnTrack(self); - if (self->open) { - mysql_close(&(self->connection)); - self->open = 0; - } - Py_CLEAR(self->converter); - MyFree(self); + PyObject_GC_UnTrack(self); + if (self->open) { + mysql_close(&(self->connection)); + self->open = 0; + } + Py_CLEAR(self->converter); + MyFree(self); } static PyObject * _mysql_ConnectionObject_repr( - _mysql_ConnectionObject *self) + _mysql_ConnectionObject *self) { - char buf[300]; - if (self->open) - sprintf(buf, "<_mysql.connection open to '%.256s' at %lx>", - self->connection.host, - (long)self); - else - sprintf(buf, "<_mysql.connection closed at %lx>", - (long)self); - return PyString_FromString(buf); + char buf[300]; + if (self->open) + sprintf(buf, "<_mysql.connection open to '%.256s' at %lx>", + self->connection.host, + (long)self); + else + sprintf(buf, "<_mysql.connection closed at %lx>", + (long)self); + return PyString_FromString(buf); } static char _mysql_ResultObject_data_seek__doc__[] = @@ -2049,623 +2049,623 @@ _mysql_ResultObject_data_seek( _mysql_ResultObject *self, PyObject *args) { - unsigned int row; - if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; - check_result_connection(self); - mysql_data_seek(self->result, row); - Py_INCREF(Py_None); - return Py_None; + unsigned int row; + if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; + check_result_connection(self); + mysql_data_seek(self->result, row); + Py_INCREF(Py_None); + return Py_None; } static void _mysql_ResultObject_dealloc( - _mysql_ResultObject *self) + _mysql_ResultObject *self) { - PyObject_GC_UnTrack((PyObject *)self); - mysql_free_result(self->result); - _mysql_ResultObject_clear(self); - MyFree(self); + PyObject_GC_UnTrack((PyObject *)self); + mysql_free_result(self->result); + _mysql_ResultObject_clear(self); + MyFree(self); } static PyObject * _mysql_ResultObject_repr( - _mysql_ResultObject *self) + _mysql_ResultObject *self) { - char buf[300]; - sprintf(buf, "<_mysql.result object at %lx>", (long)self); - return PyString_FromString(buf); + char buf[300]; + sprintf(buf, "<_mysql.result object at %lx>", (long)self); + return PyString_FromString(buf); } static PyMethodDef _mysql_ConnectionObject_methods[] = { - { - "affected_rows", - (PyCFunction)_mysql_ConnectionObject_affected_rows, - METH_NOARGS, - _mysql_ConnectionObject_affected_rows__doc__ - }, - { - "autocommit", - (PyCFunction)_mysql_ConnectionObject_autocommit, - METH_VARARGS, - _mysql_ConnectionObject_autocommit__doc__ - }, - { - "get_autocommit", - (PyCFunction)_mysql_ConnectionObject_get_autocommit, - METH_NOARGS, - _mysql_ConnectionObject_get_autocommit__doc__ - }, - { - "commit", - (PyCFunction)_mysql_ConnectionObject_commit, - METH_NOARGS, - _mysql_ConnectionObject_commit__doc__ - }, - { - "rollback", - (PyCFunction)_mysql_ConnectionObject_rollback, - METH_NOARGS, - _mysql_ConnectionObject_rollback__doc__ - }, - { - "next_result", - (PyCFunction)_mysql_ConnectionObject_next_result, - METH_NOARGS, - _mysql_ConnectionObject_next_result__doc__ - }, - { - "set_server_option", - (PyCFunction)_mysql_ConnectionObject_set_server_option, - METH_VARARGS, - _mysql_ConnectionObject_set_server_option__doc__ - }, - { - "sqlstate", - (PyCFunction)_mysql_ConnectionObject_sqlstate, - METH_NOARGS, - _mysql_ConnectionObject_sqlstate__doc__ - }, - { - "warning_count", - (PyCFunction)_mysql_ConnectionObject_warning_count, - METH_NOARGS, - _mysql_ConnectionObject_warning_count__doc__ - }, - { - "change_user", - (PyCFunction)_mysql_ConnectionObject_change_user, - METH_VARARGS | METH_KEYWORDS, - _mysql_ConnectionObject_change_user__doc__ - }, - { - "character_set_name", - (PyCFunction)_mysql_ConnectionObject_character_set_name, - METH_NOARGS, - _mysql_ConnectionObject_character_set_name__doc__ - }, - { - "set_character_set", - (PyCFunction)_mysql_ConnectionObject_set_character_set, - METH_VARARGS, - _mysql_ConnectionObject_set_character_set__doc__ - }, + { + "affected_rows", + (PyCFunction)_mysql_ConnectionObject_affected_rows, + METH_NOARGS, + _mysql_ConnectionObject_affected_rows__doc__ + }, + { + "autocommit", + (PyCFunction)_mysql_ConnectionObject_autocommit, + METH_VARARGS, + _mysql_ConnectionObject_autocommit__doc__ + }, + { + "get_autocommit", + (PyCFunction)_mysql_ConnectionObject_get_autocommit, + METH_NOARGS, + _mysql_ConnectionObject_get_autocommit__doc__ + }, + { + "commit", + (PyCFunction)_mysql_ConnectionObject_commit, + METH_NOARGS, + _mysql_ConnectionObject_commit__doc__ + }, + { + "rollback", + (PyCFunction)_mysql_ConnectionObject_rollback, + METH_NOARGS, + _mysql_ConnectionObject_rollback__doc__ + }, + { + "next_result", + (PyCFunction)_mysql_ConnectionObject_next_result, + METH_NOARGS, + _mysql_ConnectionObject_next_result__doc__ + }, + { + "set_server_option", + (PyCFunction)_mysql_ConnectionObject_set_server_option, + METH_VARARGS, + _mysql_ConnectionObject_set_server_option__doc__ + }, + { + "sqlstate", + (PyCFunction)_mysql_ConnectionObject_sqlstate, + METH_NOARGS, + _mysql_ConnectionObject_sqlstate__doc__ + }, + { + "warning_count", + (PyCFunction)_mysql_ConnectionObject_warning_count, + METH_NOARGS, + _mysql_ConnectionObject_warning_count__doc__ + }, + { + "change_user", + (PyCFunction)_mysql_ConnectionObject_change_user, + METH_VARARGS | METH_KEYWORDS, + _mysql_ConnectionObject_change_user__doc__ + }, + { + "character_set_name", + (PyCFunction)_mysql_ConnectionObject_character_set_name, + METH_NOARGS, + _mysql_ConnectionObject_character_set_name__doc__ + }, + { + "set_character_set", + (PyCFunction)_mysql_ConnectionObject_set_character_set, + METH_VARARGS, + _mysql_ConnectionObject_set_character_set__doc__ + }, #if MYSQL_VERSION_ID >= 50010 - { - "get_character_set_info", - (PyCFunction)_mysql_ConnectionObject_get_character_set_info, - METH_NOARGS, - _mysql_ConnectionObject_get_character_set_info__doc__ - }, + { + "get_character_set_info", + (PyCFunction)_mysql_ConnectionObject_get_character_set_info, + METH_NOARGS, + _mysql_ConnectionObject_get_character_set_info__doc__ + }, #endif - { - "_get_native_connection", - (PyCFunction)_mysql_ConnectionObject_get_native_connection, - METH_NOARGS, - _mysql_ConnectionObject_get_native_connection__doc__ - }, - { - "close", - (PyCFunction)_mysql_ConnectionObject_close, - METH_NOARGS, - _mysql_ConnectionObject_close__doc__ - }, - { - "fileno", - (PyCFunction)_mysql_ConnectionObject_fileno, - METH_NOARGS, - _mysql_ConnectionObject_fileno__doc__ - }, - { - "dump_debug_info", - (PyCFunction)_mysql_ConnectionObject_dump_debug_info, - METH_NOARGS, - _mysql_ConnectionObject_dump_debug_info__doc__ - }, - { - "escape", - (PyCFunction)_mysql_escape, - METH_VARARGS, - _mysql_escape__doc__ - }, - { - "escape_string", - (PyCFunction)_mysql_escape_string, - METH_VARARGS, - _mysql_escape_string__doc__ - }, - { - "error", - (PyCFunction)_mysql_ConnectionObject_error, - METH_NOARGS, - _mysql_ConnectionObject_error__doc__ - }, - { - "errno", - (PyCFunction)_mysql_ConnectionObject_errno, - METH_NOARGS, - _mysql_ConnectionObject_errno__doc__ - }, - { - "field_count", - (PyCFunction)_mysql_ConnectionObject_field_count, - METH_NOARGS, - _mysql_ConnectionObject_field_count__doc__ - }, - { - "get_host_info", - (PyCFunction)_mysql_ConnectionObject_get_host_info, - METH_NOARGS, - _mysql_ConnectionObject_get_host_info__doc__ - }, - { - "get_proto_info", - (PyCFunction)_mysql_ConnectionObject_get_proto_info, - METH_NOARGS, - _mysql_ConnectionObject_get_proto_info__doc__ - }, - { - "get_server_info", - (PyCFunction)_mysql_ConnectionObject_get_server_info, - METH_NOARGS, - _mysql_ConnectionObject_get_server_info__doc__ - }, - { - "info", - (PyCFunction)_mysql_ConnectionObject_info, - METH_NOARGS, - _mysql_ConnectionObject_info__doc__ - }, - { - "insert_id", - (PyCFunction)_mysql_ConnectionObject_insert_id, - METH_NOARGS, - _mysql_ConnectionObject_insert_id__doc__ - }, - { - "kill", - (PyCFunction)_mysql_ConnectionObject_kill, - METH_VARARGS, - _mysql_ConnectionObject_kill__doc__ - }, - { - "ping", - (PyCFunction)_mysql_ConnectionObject_ping, - METH_VARARGS, - _mysql_ConnectionObject_ping__doc__ - }, - { - "query", - (PyCFunction)_mysql_ConnectionObject_query, - METH_VARARGS, - _mysql_ConnectionObject_query__doc__ - }, - { - "send_query", - (PyCFunction)_mysql_ConnectionObject_send_query, - METH_VARARGS, - _mysql_ConnectionObject_send_query__doc__, - }, - { - "read_query_result", - (PyCFunction)_mysql_ConnectionObject_read_query_result, - METH_NOARGS, - _mysql_ConnectionObject_read_query_result__doc__, - }, - { - "select_db", - (PyCFunction)_mysql_ConnectionObject_select_db, - METH_VARARGS, - _mysql_ConnectionObject_select_db__doc__ - }, - { - "shutdown", - (PyCFunction)_mysql_ConnectionObject_shutdown, - METH_NOARGS, - _mysql_ConnectionObject_shutdown__doc__ - }, - { - "stat", - (PyCFunction)_mysql_ConnectionObject_stat, - METH_NOARGS, - _mysql_ConnectionObject_stat__doc__ - }, - { - "store_result", - (PyCFunction)_mysql_ConnectionObject_store_result, - METH_NOARGS, - _mysql_ConnectionObject_store_result__doc__ - }, - { - "string_literal", - (PyCFunction)_mysql_string_literal, - METH_VARARGS, - _mysql_string_literal__doc__}, - { - "thread_id", - (PyCFunction)_mysql_ConnectionObject_thread_id, - METH_NOARGS, - _mysql_ConnectionObject_thread_id__doc__ - }, - { - "use_result", - (PyCFunction)_mysql_ConnectionObject_use_result, - METH_NOARGS, - _mysql_ConnectionObject_use_result__doc__ - }, - {NULL, NULL} /* sentinel */ + { + "_get_native_connection", + (PyCFunction)_mysql_ConnectionObject_get_native_connection, + METH_NOARGS, + _mysql_ConnectionObject_get_native_connection__doc__ + }, + { + "close", + (PyCFunction)_mysql_ConnectionObject_close, + METH_NOARGS, + _mysql_ConnectionObject_close__doc__ + }, + { + "fileno", + (PyCFunction)_mysql_ConnectionObject_fileno, + METH_NOARGS, + _mysql_ConnectionObject_fileno__doc__ + }, + { + "dump_debug_info", + (PyCFunction)_mysql_ConnectionObject_dump_debug_info, + METH_NOARGS, + _mysql_ConnectionObject_dump_debug_info__doc__ + }, + { + "escape", + (PyCFunction)_mysql_escape, + METH_VARARGS, + _mysql_escape__doc__ + }, + { + "escape_string", + (PyCFunction)_mysql_escape_string, + METH_VARARGS, + _mysql_escape_string__doc__ + }, + { + "error", + (PyCFunction)_mysql_ConnectionObject_error, + METH_NOARGS, + _mysql_ConnectionObject_error__doc__ + }, + { + "errno", + (PyCFunction)_mysql_ConnectionObject_errno, + METH_NOARGS, + _mysql_ConnectionObject_errno__doc__ + }, + { + "field_count", + (PyCFunction)_mysql_ConnectionObject_field_count, + METH_NOARGS, + _mysql_ConnectionObject_field_count__doc__ + }, + { + "get_host_info", + (PyCFunction)_mysql_ConnectionObject_get_host_info, + METH_NOARGS, + _mysql_ConnectionObject_get_host_info__doc__ + }, + { + "get_proto_info", + (PyCFunction)_mysql_ConnectionObject_get_proto_info, + METH_NOARGS, + _mysql_ConnectionObject_get_proto_info__doc__ + }, + { + "get_server_info", + (PyCFunction)_mysql_ConnectionObject_get_server_info, + METH_NOARGS, + _mysql_ConnectionObject_get_server_info__doc__ + }, + { + "info", + (PyCFunction)_mysql_ConnectionObject_info, + METH_NOARGS, + _mysql_ConnectionObject_info__doc__ + }, + { + "insert_id", + (PyCFunction)_mysql_ConnectionObject_insert_id, + METH_NOARGS, + _mysql_ConnectionObject_insert_id__doc__ + }, + { + "kill", + (PyCFunction)_mysql_ConnectionObject_kill, + METH_VARARGS, + _mysql_ConnectionObject_kill__doc__ + }, + { + "ping", + (PyCFunction)_mysql_ConnectionObject_ping, + METH_VARARGS, + _mysql_ConnectionObject_ping__doc__ + }, + { + "query", + (PyCFunction)_mysql_ConnectionObject_query, + METH_VARARGS, + _mysql_ConnectionObject_query__doc__ + }, + { + "send_query", + (PyCFunction)_mysql_ConnectionObject_send_query, + METH_VARARGS, + _mysql_ConnectionObject_send_query__doc__, + }, + { + "read_query_result", + (PyCFunction)_mysql_ConnectionObject_read_query_result, + METH_NOARGS, + _mysql_ConnectionObject_read_query_result__doc__, + }, + { + "select_db", + (PyCFunction)_mysql_ConnectionObject_select_db, + METH_VARARGS, + _mysql_ConnectionObject_select_db__doc__ + }, + { + "shutdown", + (PyCFunction)_mysql_ConnectionObject_shutdown, + METH_NOARGS, + _mysql_ConnectionObject_shutdown__doc__ + }, + { + "stat", + (PyCFunction)_mysql_ConnectionObject_stat, + METH_NOARGS, + _mysql_ConnectionObject_stat__doc__ + }, + { + "store_result", + (PyCFunction)_mysql_ConnectionObject_store_result, + METH_NOARGS, + _mysql_ConnectionObject_store_result__doc__ + }, + { + "string_literal", + (PyCFunction)_mysql_string_literal, + METH_VARARGS, + _mysql_string_literal__doc__}, + { + "thread_id", + (PyCFunction)_mysql_ConnectionObject_thread_id, + METH_NOARGS, + _mysql_ConnectionObject_thread_id__doc__ + }, + { + "use_result", + (PyCFunction)_mysql_ConnectionObject_use_result, + METH_NOARGS, + _mysql_ConnectionObject_use_result__doc__ + }, + {NULL, NULL} /* sentinel */ }; static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { - { - "open", - T_INT, - offsetof(_mysql_ConnectionObject,open), - READONLY, - "True if connection is open" - }, - { - "converter", - T_OBJECT, - offsetof(_mysql_ConnectionObject,converter), - 0, - "Type conversion mapping" - }, - { - "server_capabilities", - T_UINT, - offsetof(_mysql_ConnectionObject,connection.server_capabilities), - READONLY, - "Capabilities of server; consult MySQLdb.constants.CLIENT" - }, - { - "port", - T_UINT, - offsetof(_mysql_ConnectionObject,connection.port), - READONLY, - "TCP/IP port of the server connection" - }, - { - "client_flag", - T_UINT, - offsetof(_mysql_ConnectionObject,connection.client_flag), - READONLY, - "Client flags; refer to MySQLdb.constants.CLIENT" - }, - {NULL} /* Sentinel */ + { + "open", + T_INT, + offsetof(_mysql_ConnectionObject,open), + READONLY, + "True if connection is open" + }, + { + "converter", + T_OBJECT, + offsetof(_mysql_ConnectionObject,converter), + 0, + "Type conversion mapping" + }, + { + "server_capabilities", + T_UINT, + offsetof(_mysql_ConnectionObject,connection.server_capabilities), + READONLY, + "Capabilities of server; consult MySQLdb.constants.CLIENT" + }, + { + "port", + T_UINT, + offsetof(_mysql_ConnectionObject,connection.port), + READONLY, + "TCP/IP port of the server connection" + }, + { + "client_flag", + T_UINT, + offsetof(_mysql_ConnectionObject,connection.client_flag), + READONLY, + "Client flags; refer to MySQLdb.constants.CLIENT" + }, + {NULL} /* Sentinel */ }; static PyMethodDef _mysql_ResultObject_methods[] = { - { - "data_seek", - (PyCFunction)_mysql_ResultObject_data_seek, - METH_VARARGS, - _mysql_ResultObject_data_seek__doc__ - }, - { - "describe", - (PyCFunction)_mysql_ResultObject_describe, - METH_NOARGS, - _mysql_ResultObject_describe__doc__ - }, - { - "fetch_row", - (PyCFunction)_mysql_ResultObject_fetch_row, - METH_VARARGS | METH_KEYWORDS, - _mysql_ResultObject_fetch_row__doc__ - }, - { - "field_flags", - (PyCFunction)_mysql_ResultObject_field_flags, - METH_NOARGS, - _mysql_ResultObject_field_flags__doc__ - }, - { - "num_fields", - (PyCFunction)_mysql_ResultObject_num_fields, - METH_NOARGS, - _mysql_ResultObject_num_fields__doc__ - }, - { - "num_rows", - (PyCFunction)_mysql_ResultObject_num_rows, - METH_NOARGS, - _mysql_ResultObject_num_rows__doc__ - }, - {NULL, NULL} /* sentinel */ + { + "data_seek", + (PyCFunction)_mysql_ResultObject_data_seek, + METH_VARARGS, + _mysql_ResultObject_data_seek__doc__ + }, + { + "describe", + (PyCFunction)_mysql_ResultObject_describe, + METH_NOARGS, + _mysql_ResultObject_describe__doc__ + }, + { + "fetch_row", + (PyCFunction)_mysql_ResultObject_fetch_row, + METH_VARARGS | METH_KEYWORDS, + _mysql_ResultObject_fetch_row__doc__ + }, + { + "field_flags", + (PyCFunction)_mysql_ResultObject_field_flags, + METH_NOARGS, + _mysql_ResultObject_field_flags__doc__ + }, + { + "num_fields", + (PyCFunction)_mysql_ResultObject_num_fields, + METH_NOARGS, + _mysql_ResultObject_num_fields__doc__ + }, + { + "num_rows", + (PyCFunction)_mysql_ResultObject_num_rows, + METH_NOARGS, + _mysql_ResultObject_num_rows__doc__ + }, + {NULL, NULL} /* sentinel */ }; static struct PyMemberDef _mysql_ResultObject_memberlist[] = { - { - "converter", - T_OBJECT, - offsetof(_mysql_ResultObject,converter), - READONLY, - "Type conversion mapping" - }, - { - "has_next", - T_BOOL, - offsetof(_mysql_ResultObject, has_next), - READONLY, - "Has next result" - }, - {NULL} /* Sentinel */ + { + "converter", + T_OBJECT, + offsetof(_mysql_ResultObject,converter), + READONLY, + "Type conversion mapping" + }, + { + "has_next", + T_BOOL, + offsetof(_mysql_ResultObject, has_next), + READONLY, + "Has next result" + }, + {NULL} /* Sentinel */ }; static PyObject * _mysql_ConnectionObject_getattro( - _mysql_ConnectionObject *self, - PyObject *name) + _mysql_ConnectionObject *self, + PyObject *name) { - char *cname; + char *cname; #ifdef IS_PY3K - cname = PyUnicode_AsUTF8(name); + cname = PyUnicode_AsUTF8(name); #else - cname = PyString_AsString(name); + cname = PyString_AsString(name); #endif - if (strcmp(cname, "closed") == 0) - return PyInt_FromLong((long)!(self->open)); + if (strcmp(cname, "closed") == 0) + return PyInt_FromLong((long)!(self->open)); - return PyObject_GenericGetAttr((PyObject *)self, name); + return PyObject_GenericGetAttr((PyObject *)self, name); } static int _mysql_ConnectionObject_setattro( - _mysql_ConnectionObject *self, - PyObject *name, - PyObject *v) -{ - if (v == NULL) { - PyErr_SetString(PyExc_AttributeError, - "can't delete connection attributes"); - return -1; - } - return PyObject_GenericSetAttr((PyObject *)self, name, v); + _mysql_ConnectionObject *self, + PyObject *name, + PyObject *v) +{ + if (v == NULL) { + PyErr_SetString(PyExc_AttributeError, + "can't delete connection attributes"); + return -1; + } + return PyObject_GenericSetAttr((PyObject *)self, name, v); } static int _mysql_ResultObject_setattro( - _mysql_ResultObject *self, - PyObject *name, - PyObject *v) -{ - if (v == NULL) { - PyErr_SetString(PyExc_AttributeError, - "can't delete connection attributes"); - return -1; - } - return PyObject_GenericSetAttr((PyObject *)self, name, v); + _mysql_ResultObject *self, + PyObject *name, + PyObject *v) +{ + if (v == NULL) { + PyErr_SetString(PyExc_AttributeError, + "can't delete connection attributes"); + return -1; + } + return PyObject_GenericSetAttr((PyObject *)self, name, v); } PyTypeObject _mysql_ConnectionObject_Type = { PyVarObject_HEAD_INIT(NULL, 0) - "_mysql.connection", /* (char *)tp_name For printing */ - sizeof(_mysql_ConnectionObject), - 0, - (destructor)_mysql_ConnectionObject_dealloc, /* tp_dealloc */ - 0, /*tp_print*/ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /*tp_compare*/ - (reprfunc)_mysql_ConnectionObject_repr, /* tp_repr */ - - /* Method suites for standard classes */ - - 0, /* (PyNumberMethods *) tp_as_number */ - 0, /* (PySequenceMethods *) tp_as_sequence */ - 0, /* (PyMappingMethods *) tp_as_mapping */ - - /* More standard operations (here for binary compatibility) */ - - 0, /* (hashfunc) tp_hash */ - 0, /* (ternaryfunc) tp_call */ - 0, /* (reprfunc) tp_str */ - (getattrofunc)_mysql_ConnectionObject_getattro, /* tp_getattro */ - (setattrofunc)_mysql_ConnectionObject_setattro, /* tp_setattro */ - - /* Functions to access object as input/output buffer */ - 0, /* (PyBufferProcs *) tp_as_buffer */ - - /* (tp_flags) Flags to define presence of optional/expanded features */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, - _mysql_connect__doc__, /* (char *) tp_doc Documentation string */ - - /* call function for all accessible objects */ - (traverseproc) _mysql_ConnectionObject_traverse, /* tp_traverse */ - - /* delete references to contained objects */ - (inquiry) _mysql_ConnectionObject_clear, /* tp_clear */ - - /* rich comparisons */ - 0, /* (richcmpfunc) tp_richcompare */ - - /* weak reference enabler */ - 0, /* (long) tp_weaklistoffset */ - - /* Iterators */ - 0, /* (getiterfunc) tp_iter */ - 0, /* (iternextfunc) tp_iternext */ - - /* Attribute descriptor and subclassing stuff */ - (struct PyMethodDef *)_mysql_ConnectionObject_methods, /* tp_methods */ - (struct PyMemberDef *)_mysql_ConnectionObject_memberlist, /* tp_members */ - 0, /* (struct getsetlist *) tp_getset; */ - 0, /* (struct _typeobject *) tp_base; */ - 0, /* (PyObject *) tp_dict */ - 0, /* (descrgetfunc) tp_descr_get */ - 0, /* (descrsetfunc) tp_descr_set */ - 0, /* (long) tp_dictoffset */ - (initproc)_mysql_ConnectionObject_Initialize, /* tp_init */ - NULL, /* tp_alloc */ - NULL, /* tp_new */ - NULL, /* tp_free Low-level free-memory routine */ - 0, /* (PyObject *) tp_bases */ - 0, /* (PyObject *) tp_mro method resolution order */ - 0, /* (PyObject *) tp_defined */ + "_mysql.connection", /* (char *)tp_name For printing */ + sizeof(_mysql_ConnectionObject), + 0, + (destructor)_mysql_ConnectionObject_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /*tp_compare*/ + (reprfunc)_mysql_ConnectionObject_repr, /* tp_repr */ + + /* Method suites for standard classes */ + + 0, /* (PyNumberMethods *) tp_as_number */ + 0, /* (PySequenceMethods *) tp_as_sequence */ + 0, /* (PyMappingMethods *) tp_as_mapping */ + + /* More standard operations (here for binary compatibility) */ + + 0, /* (hashfunc) tp_hash */ + 0, /* (ternaryfunc) tp_call */ + 0, /* (reprfunc) tp_str */ + (getattrofunc)_mysql_ConnectionObject_getattro, /* tp_getattro */ + (setattrofunc)_mysql_ConnectionObject_setattro, /* tp_setattro */ + + /* Functions to access object as input/output buffer */ + 0, /* (PyBufferProcs *) tp_as_buffer */ + + /* (tp_flags) Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, + _mysql_connect__doc__, /* (char *) tp_doc Documentation string */ + + /* call function for all accessible objects */ + (traverseproc) _mysql_ConnectionObject_traverse, /* tp_traverse */ + + /* delete references to contained objects */ + (inquiry) _mysql_ConnectionObject_clear, /* tp_clear */ + + /* rich comparisons */ + 0, /* (richcmpfunc) tp_richcompare */ + + /* weak reference enabler */ + 0, /* (long) tp_weaklistoffset */ + + /* Iterators */ + 0, /* (getiterfunc) tp_iter */ + 0, /* (iternextfunc) tp_iternext */ + + /* Attribute descriptor and subclassing stuff */ + (struct PyMethodDef *)_mysql_ConnectionObject_methods, /* tp_methods */ + (struct PyMemberDef *)_mysql_ConnectionObject_memberlist, /* tp_members */ + 0, /* (struct getsetlist *) tp_getset; */ + 0, /* (struct _typeobject *) tp_base; */ + 0, /* (PyObject *) tp_dict */ + 0, /* (descrgetfunc) tp_descr_get */ + 0, /* (descrsetfunc) tp_descr_set */ + 0, /* (long) tp_dictoffset */ + (initproc)_mysql_ConnectionObject_Initialize, /* tp_init */ + NULL, /* tp_alloc */ + NULL, /* tp_new */ + NULL, /* tp_free Low-level free-memory routine */ + 0, /* (PyObject *) tp_bases */ + 0, /* (PyObject *) tp_mro method resolution order */ + 0, /* (PyObject *) tp_defined */ } ; PyTypeObject _mysql_ResultObject_Type = { PyVarObject_HEAD_INIT(NULL, 0) - "_mysql.result", - sizeof(_mysql_ResultObject), - 0, - (destructor)_mysql_ResultObject_dealloc, /* tp_dealloc */ - 0, /*tp_print*/ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /*tp_compare*/ - (reprfunc)_mysql_ResultObject_repr, /* tp_repr */ - - /* Method suites for standard classes */ - - 0, /* (PyNumberMethods *) tp_as_number */ - 0, /* (PySequenceMethods *) tp_as_sequence */ - 0, /* (PyMappingMethods *) tp_as_mapping */ - - /* More standard operations (here for binary compatibility) */ - - 0, /* (hashfunc) tp_hash */ - 0, /* (ternaryfunc) tp_call */ - 0, /* (reprfunc) tp_str */ - (getattrofunc)PyObject_GenericGetAttr, /* tp_getattro */ - (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ - - /* Functions to access object as input/output buffer */ - 0, /* (PyBufferProcs *) tp_as_buffer */ - - /* Flags to define presence of optional/expanded features */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, - - _mysql_ResultObject__doc__, /* (char *) tp_doc Documentation string */ - - /* call function for all accessible objects */ - (traverseproc) _mysql_ResultObject_traverse, /* tp_traverse */ - - /* delete references to contained objects */ - (inquiry) _mysql_ResultObject_clear, /* tp_clear */ - - /* rich comparisons */ - 0, /* (richcmpfunc) tp_richcompare */ - - /* weak reference enabler */ - 0, /* (long) tp_weaklistoffset */ - - /* Iterators */ - 0, /* (getiterfunc) tp_iter */ - 0, /* (iternextfunc) tp_iternext */ - - /* Attribute descriptor and subclassing stuff */ - (struct PyMethodDef *) _mysql_ResultObject_methods, /* tp_methods */ - (struct PyMemberDef *) _mysql_ResultObject_memberlist, /*tp_members */ - 0, /* (struct getsetlist *) tp_getset; */ - 0, /* (struct _typeobject *) tp_base; */ - 0, /* (PyObject *) tp_dict */ - 0, /* (descrgetfunc) tp_descr_get */ - 0, /* (descrsetfunc) tp_descr_set */ - 0, /* (long) tp_dictoffset */ - (initproc)_mysql_ResultObject_Initialize, /* tp_init */ - NULL, /* tp_alloc */ - NULL, /* tp_new */ - NULL, /* tp_free Low-level free-memory routine */ - 0, /* (PyObject *) tp_bases */ - 0, /* (PyObject *) tp_mro method resolution order */ - 0, /* (PyObject *) tp_defined */ + "_mysql.result", + sizeof(_mysql_ResultObject), + 0, + (destructor)_mysql_ResultObject_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /*tp_compare*/ + (reprfunc)_mysql_ResultObject_repr, /* tp_repr */ + + /* Method suites for standard classes */ + + 0, /* (PyNumberMethods *) tp_as_number */ + 0, /* (PySequenceMethods *) tp_as_sequence */ + 0, /* (PyMappingMethods *) tp_as_mapping */ + + /* More standard operations (here for binary compatibility) */ + + 0, /* (hashfunc) tp_hash */ + 0, /* (ternaryfunc) tp_call */ + 0, /* (reprfunc) tp_str */ + (getattrofunc)PyObject_GenericGetAttr, /* tp_getattro */ + (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ + + /* Functions to access object as input/output buffer */ + 0, /* (PyBufferProcs *) tp_as_buffer */ + + /* Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, + + _mysql_ResultObject__doc__, /* (char *) tp_doc Documentation string */ + + /* call function for all accessible objects */ + (traverseproc) _mysql_ResultObject_traverse, /* tp_traverse */ + + /* delete references to contained objects */ + (inquiry) _mysql_ResultObject_clear, /* tp_clear */ + + /* rich comparisons */ + 0, /* (richcmpfunc) tp_richcompare */ + + /* weak reference enabler */ + 0, /* (long) tp_weaklistoffset */ + + /* Iterators */ + 0, /* (getiterfunc) tp_iter */ + 0, /* (iternextfunc) tp_iternext */ + + /* Attribute descriptor and subclassing stuff */ + (struct PyMethodDef *) _mysql_ResultObject_methods, /* tp_methods */ + (struct PyMemberDef *) _mysql_ResultObject_memberlist, /*tp_members */ + 0, /* (struct getsetlist *) tp_getset; */ + 0, /* (struct _typeobject *) tp_base; */ + 0, /* (PyObject *) tp_dict */ + 0, /* (descrgetfunc) tp_descr_get */ + 0, /* (descrsetfunc) tp_descr_set */ + 0, /* (long) tp_dictoffset */ + (initproc)_mysql_ResultObject_Initialize, /* tp_init */ + NULL, /* tp_alloc */ + NULL, /* tp_new */ + NULL, /* tp_free Low-level free-memory routine */ + 0, /* (PyObject *) tp_bases */ + 0, /* (PyObject *) tp_mro method resolution order */ + 0, /* (PyObject *) tp_defined */ }; static PyMethodDef _mysql_methods[] = { - { - "connect", - (PyCFunction)_mysql_connect, - METH_VARARGS | METH_KEYWORDS, - _mysql_connect__doc__ - }, - { - "debug", - (PyCFunction)_mysql_debug, - METH_VARARGS, - _mysql_debug__doc__ - }, - { - "escape", - (PyCFunction)_mysql_escape, - METH_VARARGS, - _mysql_escape__doc__ - }, - { - // deprecated. - "escape_sequence", - (PyCFunction)_mysql_escape_sequence, - METH_VARARGS, - _mysql_escape_sequence__doc__ - }, - { - // deprecated. - "escape_dict", - (PyCFunction)_mysql_escape_dict, - METH_VARARGS, - _mysql_escape_dict__doc__ - }, - { - "escape_string", - (PyCFunction)_mysql_escape_string, - METH_VARARGS, - _mysql_escape_string__doc__ - }, - { - "string_literal", - (PyCFunction)_mysql_string_literal, - METH_VARARGS, - _mysql_string_literal__doc__ - }, - { - "get_client_info", - (PyCFunction)_mysql_get_client_info, - METH_NOARGS, - _mysql_get_client_info__doc__ - }, - { - "thread_safe", - (PyCFunction)_mysql_thread_safe, - METH_NOARGS, - _mysql_thread_safe__doc__ - }, - {NULL, NULL} /* sentinel */ + { + "connect", + (PyCFunction)_mysql_connect, + METH_VARARGS | METH_KEYWORDS, + _mysql_connect__doc__ + }, + { + "debug", + (PyCFunction)_mysql_debug, + METH_VARARGS, + _mysql_debug__doc__ + }, + { + "escape", + (PyCFunction)_mysql_escape, + METH_VARARGS, + _mysql_escape__doc__ + }, + { + // deprecated. + "escape_sequence", + (PyCFunction)_mysql_escape_sequence, + METH_VARARGS, + _mysql_escape_sequence__doc__ + }, + { + // deprecated. + "escape_dict", + (PyCFunction)_mysql_escape_dict, + METH_VARARGS, + _mysql_escape_dict__doc__ + }, + { + "escape_string", + (PyCFunction)_mysql_escape_string, + METH_VARARGS, + _mysql_escape_string__doc__ + }, + { + "string_literal", + (PyCFunction)_mysql_string_literal, + METH_VARARGS, + _mysql_string_literal__doc__ + }, + { + "get_client_info", + (PyCFunction)_mysql_get_client_info, + METH_NOARGS, + _mysql_get_client_info__doc__ + }, + { + "thread_safe", + (PyCFunction)_mysql_thread_safe, + METH_NOARGS, + _mysql_thread_safe__doc__ + }, + {NULL, NULL} /* sentinel */ }; static PyObject * _mysql_NewException( - PyObject *dict, - PyObject *edict, - char *name) + PyObject *dict, + PyObject *edict, + char *name) { - PyObject *e; - if (!(e = PyDict_GetItemString(edict, name))) - return NULL; - if (PyDict_SetItemString(dict, name, e)) - return NULL; - Py_INCREF(e); - return e; + PyObject *e; + if (!(e = PyDict_GetItemString(edict, name))) + return NULL; + if (PyDict_SetItemString(dict, name, e)) + return NULL; + Py_INCREF(e); + return e; } #define QUOTE(X) _QUOTE(X) @@ -2702,100 +2702,100 @@ DL_EXPORT(void) init_mysql(void) #endif { - PyObject *dict, *module, *emod, *edict; + PyObject *dict, *module, *emod, *edict; #ifndef IS_PY3K - _mysql_ConnectionObject_Type.ob_type = &PyType_Type; - _mysql_ResultObject_Type.ob_type = &PyType_Type; + _mysql_ConnectionObject_Type.ob_type = &PyType_Type; + _mysql_ResultObject_Type.ob_type = &PyType_Type; #endif #if PY_VERSION_HEX >= 0x02020000 - _mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc; - _mysql_ConnectionObject_Type.tp_new = PyType_GenericNew; - _mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc; - _mysql_ResultObject_Type.tp_new = PyType_GenericNew; + _mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc; + _mysql_ConnectionObject_Type.tp_new = PyType_GenericNew; + _mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc; + _mysql_ResultObject_Type.tp_new = PyType_GenericNew; #endif #ifdef IS_PY3K - if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) - return NULL; - if (PyType_Ready(&_mysql_ResultObject_Type) < 0) - return NULL; + if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) + return NULL; + if (PyType_Ready(&_mysql_ResultObject_Type) < 0) + return NULL; - module = PyModule_Create(&_mysqlmodule); - if (!module) return module; /* this really should never happen */ + module = PyModule_Create(&_mysqlmodule); + if (!module) return module; /* this really should never happen */ #else - _mysql_ConnectionObject_Type.tp_free = _PyObject_GC_Del; - _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; - module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, - (PyObject *)NULL, PYTHON_API_VERSION); - if (!module) return; /* this really should never happen */ + _mysql_ConnectionObject_Type.tp_free = _PyObject_GC_Del; + _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; + module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, + (PyObject *)NULL, PYTHON_API_VERSION); + if (!module) return; /* this really should never happen */ #endif - if (!(dict = PyModule_GetDict(module))) goto error; - if (PyDict_SetItemString(dict, "version_info", - PyRun_String(QUOTE(version_info), Py_eval_input, - dict, dict))) - goto error; - if (PyDict_SetItemString(dict, "__version__", - PyString_FromString(QUOTE(__version__)))) - goto error; - if (PyDict_SetItemString(dict, "connection", - (PyObject *)&_mysql_ConnectionObject_Type)) - goto error; - Py_INCREF(&_mysql_ConnectionObject_Type); - if (PyDict_SetItemString(dict, "result", - (PyObject *)&_mysql_ResultObject_Type)) - goto error; - Py_INCREF(&_mysql_ResultObject_Type); - if (!(emod = PyImport_ImportModule("_mysql_exceptions"))) { - PyErr_Print(); - goto error; - } - if (!(edict = PyModule_GetDict(emod))) goto error; - if (!(_mysql_MySQLError = - _mysql_NewException(dict, edict, "MySQLError"))) - goto error; - if (!(_mysql_Warning = - _mysql_NewException(dict, edict, "Warning"))) - goto error; - if (!(_mysql_Error = - _mysql_NewException(dict, edict, "Error"))) - goto error; - if (!(_mysql_InterfaceError = - _mysql_NewException(dict, edict, "InterfaceError"))) - goto error; - if (!(_mysql_DatabaseError = - _mysql_NewException(dict, edict, "DatabaseError"))) - goto error; - if (!(_mysql_DataError = - _mysql_NewException(dict, edict, "DataError"))) - goto error; - if (!(_mysql_OperationalError = - _mysql_NewException(dict, edict, "OperationalError"))) - goto error; - if (!(_mysql_IntegrityError = - _mysql_NewException(dict, edict, "IntegrityError"))) - goto error; - if (!(_mysql_InternalError = - _mysql_NewException(dict, edict, "InternalError"))) - goto error; - if (!(_mysql_ProgrammingError = - _mysql_NewException(dict, edict, "ProgrammingError"))) - goto error; - if (!(_mysql_NotSupportedError = - _mysql_NewException(dict, edict, "NotSupportedError"))) - goto error; - Py_DECREF(emod); - if (!(_mysql_NULL = PyString_FromString("NULL"))) - goto error; - if (PyDict_SetItemString(dict, "NULL", _mysql_NULL)) goto error; + if (!(dict = PyModule_GetDict(module))) goto error; + if (PyDict_SetItemString(dict, "version_info", + PyRun_String(QUOTE(version_info), Py_eval_input, + dict, dict))) + goto error; + if (PyDict_SetItemString(dict, "__version__", + PyString_FromString(QUOTE(__version__)))) + goto error; + if (PyDict_SetItemString(dict, "connection", + (PyObject *)&_mysql_ConnectionObject_Type)) + goto error; + Py_INCREF(&_mysql_ConnectionObject_Type); + if (PyDict_SetItemString(dict, "result", + (PyObject *)&_mysql_ResultObject_Type)) + goto error; + Py_INCREF(&_mysql_ResultObject_Type); + if (!(emod = PyImport_ImportModule("_mysql_exceptions"))) { + PyErr_Print(); + goto error; + } + if (!(edict = PyModule_GetDict(emod))) goto error; + if (!(_mysql_MySQLError = + _mysql_NewException(dict, edict, "MySQLError"))) + goto error; + if (!(_mysql_Warning = + _mysql_NewException(dict, edict, "Warning"))) + goto error; + if (!(_mysql_Error = + _mysql_NewException(dict, edict, "Error"))) + goto error; + if (!(_mysql_InterfaceError = + _mysql_NewException(dict, edict, "InterfaceError"))) + goto error; + if (!(_mysql_DatabaseError = + _mysql_NewException(dict, edict, "DatabaseError"))) + goto error; + if (!(_mysql_DataError = + _mysql_NewException(dict, edict, "DataError"))) + goto error; + if (!(_mysql_OperationalError = + _mysql_NewException(dict, edict, "OperationalError"))) + goto error; + if (!(_mysql_IntegrityError = + _mysql_NewException(dict, edict, "IntegrityError"))) + goto error; + if (!(_mysql_InternalError = + _mysql_NewException(dict, edict, "InternalError"))) + goto error; + if (!(_mysql_ProgrammingError = + _mysql_NewException(dict, edict, "ProgrammingError"))) + goto error; + if (!(_mysql_NotSupportedError = + _mysql_NewException(dict, edict, "NotSupportedError"))) + goto error; + Py_DECREF(emod); + if (!(_mysql_NULL = PyString_FromString("NULL"))) + goto error; + if (PyDict_SetItemString(dict, "NULL", _mysql_NULL)) goto error; error: - if (PyErr_Occurred()) { - PyErr_SetString(PyExc_ImportError, - "_mysql: init failed"); - module = NULL; + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_ImportError, "_mysql: init failed"); + module = NULL; } #ifdef IS_PY3K return module; #endif } -/* vim: set ts=4 sts=4 sw=4 noexpandtab : */ + +/* vim: set ts=4 sts=4 sw=4 expandtab : */ From bd62c5d5d73369523cd8834763e1c6a11aa1587c Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 19:36:33 +0900 Subject: [PATCH 123/396] cursor: Remove deprecated methods and classes --- MySQLdb/cursors.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 9181f237..98b3c738 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -534,38 +534,6 @@ class CursorDictRowsMixIn(object): _fetch_type = 1 - def fetchoneDict(self): - """Fetch a single row as a dictionary. Deprecated: - Use fetchone() instead. Will be removed in 1.3.""" - from warnings import warn - warn("fetchoneDict() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - return self.fetchone() - - def fetchmanyDict(self, size=None): - """Fetch several rows as a list of dictionaries. Deprecated: - Use fetchmany() instead. Will be removed in 1.3.""" - from warnings import warn - warn("fetchmanyDict() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - return self.fetchmany(size) - - def fetchallDict(self): - """Fetch all available rows as a list of dictionaries. Deprecated: - Use fetchall() instead. Will be removed in 1.3.""" - from warnings import warn - warn("fetchallDict() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - return self.fetchall() - - -class CursorOldDictRowsMixIn(CursorDictRowsMixIn): - """This is a MixIn class that returns rows as dictionaries with - the same key convention as the old Mysqldb (MySQLmodule). Don't - use this.""" - - _fetch_type = 2 - class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, BaseCursor): @@ -589,5 +557,3 @@ class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, BaseCursor): """This is a Cursor class that returns rows as dictionaries and stores the result set in the server.""" - - From 833816ee87b4c88d77fe4c23e6368bc5a7f6e693 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 4 Dec 2018 20:19:04 +0900 Subject: [PATCH 124/396] Move _mysql and _mysql_exceptions into MySQLdb/ (#293) --- MySQLdb/__init__.py | 4 ++-- _mysql.c => MySQLdb/_mysql.c | 2 +- _mysql_exceptions.py => MySQLdb/_mysql_exceptions.py | 0 MySQLdb/connections.py | 5 ++--- MySQLdb/converters.py | 2 +- MySQLdb/cursors.py | 12 +++++++----- MySQLdb/times.py | 2 +- doc/user_guide.rst | 6 +++--- metadata.cfg | 2 +- setup.py | 4 +++- setup_posix.py | 3 +-- setup_windows.py | 3 +-- tests/test_MySQLdb_nonstandard.py | 2 +- tests/test__mysql.py | 2 +- 14 files changed, 25 insertions(+), 24 deletions(-) rename _mysql.c => MySQLdb/_mysql.c (99%) rename _mysql_exceptions.py => MySQLdb/_mysql_exceptions.py (100%) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index a4a9da94..ad2057bc 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -15,7 +15,7 @@ from MySQLdb.release import __version__, version_info, __author__ -import _mysql +from . import _mysql if version_info != _mysql.version_info: raise ImportError("this is MySQLdb version %s, but _mysql is version %r" % @@ -25,7 +25,7 @@ apilevel = "2.0" paramstyle = "format" -from _mysql import * +from ._mysql import * from MySQLdb.compat import PY2 from MySQLdb.constants import FIELD_TYPE from MySQLdb.times import Date, Time, Timestamp, \ diff --git a/_mysql.c b/MySQLdb/_mysql.c similarity index 99% rename from _mysql.c rename to MySQLdb/_mysql.c index 537e61bc..04eadb7c 100644 --- a/_mysql.c +++ b/MySQLdb/_mysql.c @@ -2746,7 +2746,7 @@ init_mysql(void) (PyObject *)&_mysql_ResultObject_Type)) goto error; Py_INCREF(&_mysql_ResultObject_Type); - if (!(emod = PyImport_ImportModule("_mysql_exceptions"))) { + if (!(emod = PyImport_ImportModule("MySQLdb._mysql_exceptions"))) { PyErr_Print(); goto error; } diff --git a/_mysql_exceptions.py b/MySQLdb/_mysql_exceptions.py similarity index 100% rename from _mysql_exceptions.py rename to MySQLdb/_mysql_exceptions.py diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 10a0bf77..98c1ebff 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -7,14 +7,13 @@ import re import sys -from MySQLdb import cursors +from MySQLdb import cursors, _mysql from MySQLdb.compat import unicode, PY2 -from _mysql_exceptions import ( +from MySQLdb._mysql_exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, NotSupportedError, ProgrammingError, ) -import _mysql if not PY2: diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 5e28005d..c13e4265 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -31,7 +31,7 @@ MySQL.connect(). """ -from _mysql import string_literal, escape, NULL +from MySQLdb._mysql import string_literal, escape, NULL from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * from MySQLdb.compat import PY2, long diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 98b3c738..60044830 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -8,8 +8,8 @@ import re import sys -from MySQLdb.compat import unicode -from _mysql_exceptions import ( +from .compat import unicode +from ._mysql_exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, NotSupportedError, ProgrammingError) @@ -55,9 +55,11 @@ class BaseCursor(object): #: Default value of max_allowed_packet is 1048576. max_stmt_length = 64*1024 - from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \ - DatabaseError, DataError, OperationalError, IntegrityError, \ - InternalError, ProgrammingError, NotSupportedError + from ._mysql_exceptions import ( + MySQLError, Warning, Error, InterfaceError, + DatabaseError, DataError, OperationalError, IntegrityError, + InternalError, ProgrammingError, NotSupportedError, + ) _defer_warnings = False connection = None diff --git a/MySQLdb/times.py b/MySQLdb/times.py index e26f8868..510d1c7c 100644 --- a/MySQLdb/times.py +++ b/MySQLdb/times.py @@ -6,7 +6,7 @@ """ from time import localtime from datetime import date, datetime, time, timedelta -from _mysql import string_literal +from MySQLdb._mysql import string_literal Date = date Time = time diff --git a/doc/user_guide.rst b/doc/user_guide.rst index d686b786..55c2c83f 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -17,11 +17,11 @@ Installation The ``README`` file has complete installation instructions. -_mysql ------- +MySQLdb._mysql +-------------- If you want to write applications which are portable across databases, -use MySQLdb_, and avoid using this module directly. ``_mysql`` +use MySQLdb_, and avoid using this module directly. ``MySQLdb._mysql`` provides an interface which mostly implements the MySQL C API. For more information, see the `MySQL documentation`_. The documentation for this module is intentionally weak because you probably should use diff --git a/metadata.cfg b/metadata.cfg index 6e05d35d..93b329b6 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -31,7 +31,7 @@ classifiers: Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: - _mysql_exceptions + MySQLdb._mysql_exceptions MySQLdb.compat MySQLdb.connections MySQLdb.converters diff --git a/setup.py b/setup.py index ca4b362e..d1029962 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,9 @@ readme = f.read() metadata, options = get_config() -metadata['ext_modules'] = [setuptools.Extension(sources=['_mysql.c'], **options)] +metadata['ext_modules'] = [ + setuptools.Extension("MySQLdb._mysql", sources=['MySQLdb/_mysql.c'], **options) +] metadata['long_description'] = readme metadata['long_description_content_type'] = "text/markdown" setuptools.setup(**metadata) diff --git a/setup_posix.py b/setup_posix.py index 7b86ec73..9289bb50 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -94,7 +94,6 @@ def get_config(): create_release_file(metadata) del metadata['version_info'] ext_options = dict( - name = "_mysql", library_dirs = library_dirs, libraries = libraries, extra_compile_args = extra_compile_args, @@ -102,7 +101,7 @@ def get_config(): include_dirs = include_dirs, extra_objects = extra_objects, define_macros = define_macros, - ) + ) # newer versions of gcc require libstdc++ if doing a static build if static: diff --git a/setup_windows.py b/setup_windows.py index 74279077..0811e127 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -35,7 +35,6 @@ def get_config(): create_release_file(metadata) del metadata['version_info'] ext_options = dict( - name = "_mysql", library_dirs = library_dirs, libraries = libraries, extra_compile_args = extra_compile_args, @@ -43,7 +42,7 @@ def get_config(): include_dirs = include_dirs, extra_objects = extra_objects, define_macros = define_macros, - ) + ) return metadata, ext_options if __name__ == "__main__": diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index 52f6a4fb..a06d5edf 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -1,6 +1,6 @@ import unittest -import _mysql +from MySQLdb import _mysql import MySQLdb from MySQLdb.constants import FIELD_TYPE from configdb import connection_factory diff --git a/tests/test__mysql.py b/tests/test__mysql.py index e2cb6088..dd024b72 100644 --- a/tests/test__mysql.py +++ b/tests/test__mysql.py @@ -1,5 +1,5 @@ import pytest -import _mysql +from MySQLdb import _mysql def test_result_type(): From 4e7df80f7fed6047ac42a5e6840d9d894c6de036 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 20:22:28 +0900 Subject: [PATCH 125/396] Add changelog --- HISTORY.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index dd3bbaeb..0d326404 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,17 @@ +====================== + What's new in 1.4.0 +====================== + +Release: TBD + +* Removed ``threadsafe`` and ``embedded`` build options. + +* Remove some deprecated cursor classes and methods. + +* ``_mysql`` and ``_mysql_exceptions`` modules are moved under + ``MySQLdb`` package. (#293) + + ====================== What's new in 1.3.14 ====================== From 0d7fcc269ce3cefc9614549e051092234a5ddb00 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 4 Dec 2018 20:36:04 +0900 Subject: [PATCH 126/396] Remove errorhandler (#294) --- HISTORY.rst | 1 + MySQLdb/connections.py | 27 --------------------------- MySQLdb/cursors.py | 18 +++++------------- 3 files changed, 6 insertions(+), 40 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0d326404..9e8a19ed 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,6 +11,7 @@ Release: TBD * ``_mysql`` and ``_mysql_exceptions`` modules are moved under ``MySQLdb`` package. (#293) +* Remove ``errorhandler`` from Connection and Cursor classes. ====================== What's new in 1.3.14 diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 98c1ebff..1629073d 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -28,31 +28,6 @@ def _fast_surrogateescape(s): return s.decode('ascii', 'surrogateescape') -def defaulterrorhandler(connection, cursor, errorclass, errorvalue): - """ - If cursor is not None, (errorclass, errorvalue) is appended to - cursor.messages; otherwise it is appended to - connection.messages. Then errorclass is raised with errorvalue as - the value. - - You can override this with your own error handler by assigning it - to the instance. - """ - error = errorclass, errorvalue - if cursor: - cursor.messages.append(error) - else: - connection.messages.append(error) - del cursor - del connection - if isinstance(errorvalue, BaseException): - raise errorvalue - if errorclass is not None: - raise errorclass(errorvalue) - else: - raise Exception(errorvalue) - - re_numeric_part = re.compile(r"^(\d+)") def numeric_part(s): @@ -394,6 +369,4 @@ def show_warnings(self): ProgrammingError = ProgrammingError NotSupportedError = NotSupportedError - errorhandler = defaulterrorhandler - # vim: colorcolumn=100 diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 60044830..f503b71c 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -73,7 +73,6 @@ def __init__(self, connection): self._executed = None self.lastrowid = None self.messages = [] - self.errorhandler = connection.errorhandler self._result = None self._warnings = None self.rownumber = None @@ -88,7 +87,6 @@ def close(self): pass finally: self.connection = None - self.errorhandler = None self._result = None def __enter__(self): @@ -126,7 +124,7 @@ def _escape_args(self, args, conn): def _check_executed(self): if not self._executed: - self.errorhandler(self, ProgrammingError, "execute() first") + raise ProgrammingError("execute() first") def _warning_check(self): from warnings import warn @@ -244,17 +242,12 @@ def execute(self, query, args=None): try: query = query % args except TypeError as m: - self.errorhandler(self, ProgrammingError, str(m)) + raise ProgrammingError(str(m)) if isinstance(query, unicode): query = query.encode(db.encoding, 'surrogateescape') - res = None - try: - res = self._query(query) - except Exception: - exc, value = sys.exc_info()[:2] - self.errorhandler(self, exc, value) + res = self._query(query) if not self._defer_warnings: self._warning_check() return res @@ -458,10 +451,9 @@ def scroll(self, value, mode='relative'): elif mode == 'absolute': r = value else: - self.errorhandler(self, ProgrammingError, - "unknown scroll mode %s" % repr(mode)) + raise ProgrammingError("unknown scroll mode %s" % repr(mode)) if r < 0 or r >= len(self._rows): - self.errorhandler(self, IndexError, "out of range") + raise IndexError("out of range") self.rownumber = r def __iter__(self): From c64915b1e5c705f4fb10e86db5dcfed0b58552cc Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 4 Dec 2018 20:53:53 +0900 Subject: [PATCH 127/396] Remove context interface from Connector (#295) --- MySQLdb/connections.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 1629073d..574f72e2 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -253,20 +253,6 @@ def query(self, query): else: _mysql.connection.query(self, query) - def __enter__(self): - from warnings import warn - warn("context interface will be changed. Use explicit conn.commit() or conn.rollback().", - DeprecationWarning, 2) - if self.get_autocommit(): - self.query("BEGIN") - return self.cursor() - - def __exit__(self, exc, value, tb): - if exc: - self.rollback() - else: - self.commit() - def _bytes_literal(self, bs): assert isinstance(bs, (bytes, bytearray)) x = self.string_literal(bs) # x is escaped and quoted bytes From f04067faa9cbf8154afa2ae28646ba0fda6e9713 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 20:55:19 +0900 Subject: [PATCH 128/396] Update history --- HISTORY.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 9e8a19ed..c6a2ca28 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -13,6 +13,10 @@ Release: TBD * Remove ``errorhandler`` from Connection and Cursor classes. +* Remove context manager API from Connection. It was for transaction. + New context manager API for closing connection will be added in future version. + + ====================== What's new in 1.3.14 ====================== From de44ff07e7c199f0c8762a8ee0bf7722100db158 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 20:59:16 +0900 Subject: [PATCH 129/396] Remove waiter option --- HISTORY.rst | 1 + MySQLdb/connections.py | 13 +------------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index c6a2ca28..a20ffe1c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -16,6 +16,7 @@ Release: TBD * Remove context manager API from Connection. It was for transaction. New context manager API for closing connection will be added in future version. +* Remove ``waiter`` option from Connection. ====================== What's new in 1.3.14 diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 574f72e2..aa9c7529 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -50,7 +50,6 @@ class Connection(_mysql.connection): """MySQL Database Connection Object""" default_cursor = cursors.Cursor - waiter = None def __init__(self, *args, **kwargs): """ @@ -173,11 +172,6 @@ class object, used to create cursors (keyword only) # PEP-249 requires autocommit to be initially off autocommit = kwargs2.pop('autocommit', False) - self.waiter = kwargs2.pop('waiter', None) - if self.waiter: - from warnings import warn - warn("waiter is deprecated and will be removed in 1.4.", - DeprecationWarning, 2) super(Connection, self).__init__(*args, **kwargs2) self.cursorclass = cursorclass @@ -246,12 +240,7 @@ def query(self, query): # Since _mysql releases GIL while querying, we need immutable buffer. if isinstance(query, bytearray): query = bytes(query) - if self.waiter is not None: - self.send_query(query) - self.waiter(self.fileno()) - self.read_query_result() - else: - _mysql.connection.query(self, query) + _mysql.connection.query(self, query) def _bytes_literal(self, bs): assert isinstance(bs, (bytes, bytearray)) From ee91bc54a156b476e1c7842a418bf0c9e63420ee Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 21:28:26 +0900 Subject: [PATCH 130/396] Remove Python 3.4 support --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a3b30d31..6a1df409 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ python: - "3.7" - "3.6" - "3.5" - - "3.4" - "2.7" cache: pip From dff668c25b68e36bdd2261b69602019f6dae1ee8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 21:31:01 +0900 Subject: [PATCH 131/396] Remove Python 3.4 classifiers --- metadata.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/metadata.cfg b/metadata.cfg index 93b329b6..6901e7af 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -24,7 +24,6 @@ classifiers: Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 From 16915a03530d73216d4342ef58d7f3ca90f19548 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 4 Dec 2018 21:50:30 +0900 Subject: [PATCH 132/396] Remove `fileno`, `escape_sequence`, and `escape_dict` from Connection (#297) --- HISTORY.rst | 3 ++ MySQLdb/__init__.py | 4 +- MySQLdb/_mysql.c | 96 --------------------------------------------- 3 files changed, 5 insertions(+), 98 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index a20ffe1c..0b5003db 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -18,6 +18,9 @@ Release: TBD * Remove ``waiter`` option from Connection. +* Remove ``fileno``, ``escape_sequence``, and ``escape_dict`` methods + from Connection class. + ====================== What's new in 1.3.14 ====================== diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index ad2057bc..fa52a83d 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -93,7 +93,7 @@ def Connect(*args, **kwargs): 'MySQLError', 'NULL', 'NUMBER', 'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect', 'connections', - 'constants', 'converters', 'cursors', 'debug', 'escape', 'escape_dict', - 'escape_sequence', 'escape_string', 'get_client_info', + 'constants', 'converters', 'cursors', 'debug', 'escape', + 'escape_string', 'get_client_info', 'paramstyle', 'string_literal', 'threadsafety', 'version_info'] diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 04eadb7c..0a527823 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -607,17 +607,6 @@ static int _mysql_ConnectionObject_clear( return 0; } -static char _mysql_ConnectionObject_fileno__doc__[] = -"Return underlaying fd for connection (deprecated)"; - -static PyObject * -_mysql_ConnectionObject_fileno( - _mysql_ConnectionObject *self) -{ - check_connection(self); - return PyInt_FromLong(self->connection.net.fd); -} - static char _mysql_ConnectionObject_close__doc__[] = "Close the connection. No further activity possible."; @@ -1036,71 +1025,6 @@ _mysql_escape( } } -static char _mysql_escape_sequence__doc__[] = -"escape_sequence(seq, dict) -- escape any special characters in sequence\n\ -seq using mapping dict to provide quoting functions for each type.\n\ -Returns a tuple of escaped items."; -static PyObject * -_mysql_escape_sequence( - PyObject *self, - PyObject *args) -{ - PyObject *o=NULL, *d=NULL, *r=NULL, *item, *quoted; - int i, n; - if (!PyArg_ParseTuple(args, "OO:escape_sequence", &o, &d)) - goto error; - if (!PyMapping_Check(d)) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } - if ((n = PyObject_Length(o)) == -1) goto error; - if (!(r = PyTuple_New(n))) goto error; - for (i=0; i Date: Tue, 4 Dec 2018 21:54:14 +0900 Subject: [PATCH 133/396] Remove automatic warning check (#296) --- MySQLdb/cursors.py | 46 ------------------------------ tests/test_MySQLdb_capabilities.py | 22 -------------- 2 files changed, 68 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index f503b71c..97908249 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -61,7 +61,6 @@ class BaseCursor(object): InternalError, ProgrammingError, NotSupportedError, ) - _defer_warnings = False connection = None def __init__(self, connection): @@ -126,40 +125,6 @@ def _check_executed(self): if not self._executed: raise ProgrammingError("execute() first") - def _warning_check(self): - from warnings import warn - db = self._get_db() - - # None => warnings not interrogated for current query yet - # 0 => no warnings exists or have been handled already for this query - if self._warnings is None: - self._warnings = db.warning_count() - if self._warnings: - # Only propagate warnings for current query once - warning_count = self._warnings - self._warnings = 0 - # When there is next result, fetching warnings cause "command - # out of sync" error. - if self._result and self._result.has_next: - msg = "There are %d MySQL warnings." % (warning_count,) - self.messages.append(msg) - warn(self.Warning(0, msg), stacklevel=3) - return - - warnings = db.show_warnings() - if warnings: - # This is done in two loops in case - # Warnings are set to raise exceptions. - for w in warnings: - self.messages.append((self.Warning, w)) - for w in warnings: - warn(self.Warning(*w[1:3]), stacklevel=3) - else: - info = db.info() - if info: - self.messages.append((self.Warning, info)) - warn(self.Warning(0, info), stacklevel=3) - def nextset(self): """Advance to the next result set. @@ -175,7 +140,6 @@ def nextset(self): return None self._do_get_result(db) self._post_get_result() - self._warning_check() return 1 def _do_get_result(self, db): @@ -248,8 +212,6 @@ def execute(self, query, args=None): query = query.encode(db.encoding, 'surrogateescape') res = self._query(query) - if not self._defer_warnings: - self._warning_check() return res def executemany(self, query, args): @@ -363,8 +325,6 @@ def callproc(self, procname, args=()): if isinstance(q, unicode): q = q.encode(db.encoding, 'surrogateescape') self._query(q) - if not self._defer_warnings: - self._warning_check() return args def _query(self, q): @@ -470,8 +430,6 @@ class CursorUseResultMixIn(object): close() the cursor before additional queries can be performed on the connection.""" - _defer_warnings = True - def _get_result(self): return self._get_db().use_result() @@ -480,7 +438,6 @@ def fetchone(self): self._check_executed() r = self._fetch_row(1) if not r: - self._warning_check() return None self.rownumber = self.rownumber + 1 return r[0] @@ -491,8 +448,6 @@ def fetchmany(self, size=None): self._check_executed() r = self._fetch_row(size or self.arraysize) self.rownumber = self.rownumber + len(r) - if not r: - self._warning_check() return r def fetchall(self): @@ -500,7 +455,6 @@ def fetchall(self): self._check_executed() r = self._fetch_row(0) self.rownumber = self.rownumber + len(r) - self._warning_check() return r def __iter__(self): diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 0427ed74..e9b0e2a9 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -160,28 +160,6 @@ def test_reraise_exception(self): return self.fail("Should raise ProgrammingError") - def test_warning_propagation(self): - with warnings.catch_warnings(): - # Ignore all warnings other than MySQLdb generated ones - warnings.simplefilter("ignore") - warnings.simplefilter("error", category=MySQLdb.Warning) - - # verify for both buffered and unbuffered cursor types - for cursor_class in (cursors.Cursor, cursors.SSCursor): - c = self.connection.cursor(cursor_class) - try: - c.execute("SELECT CAST('124b' AS SIGNED)") - c.fetchall() - except MySQLdb.Warning as e: - # Warnings should have errorcode and string message, just like exceptions - self.assertEqual(len(e.args), 2) - self.assertEqual(e.args[0], 1292) - self.assertTrue(isinstance(e.args[1], unicode)) - else: - self.fail("Should raise Warning") - finally: - c.close() - def test_binary_prefix(self): # verify prefix behaviour when enabled, disabled and for default (disabled) for binary_prefix in (True, False, None): From 2f8eaabf0d136d10709660ff122a53525bc180e5 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 21:59:36 +0900 Subject: [PATCH 134/396] history --- HISTORY.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 0b5003db..2fd4500b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -21,6 +21,9 @@ Release: TBD * Remove ``fileno``, ``escape_sequence``, and ``escape_dict`` methods from Connection class. +* Remove automatic MySQL warning checking. + + ====================== What's new in 1.3.14 ====================== From 20ee2e307b747883f1432d67b3b4c4617839769e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 22:06:08 +0900 Subject: [PATCH 135/396] Fix warnings --- MySQLdb/_mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 0a527823..ce88d03c 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -367,7 +367,7 @@ _mysql_ConnectionObject_Initialize( MYSQL *conn = NULL; PyObject *conv = NULL; PyObject *ssl = NULL; - char *key = NULL, *cert = NULL, *ca = NULL, + const char *key = NULL, *cert = NULL, *ca = NULL, *capath = NULL, *cipher = NULL; PyObject *ssl_keepref[5] = {NULL}; int n_ssl_keepref = 0; @@ -2335,7 +2335,7 @@ _mysql_ConnectionObject_getattro( _mysql_ConnectionObject *self, PyObject *name) { - char *cname; + const char *cname; #ifdef IS_PY3K cname = PyUnicode_AsUTF8(name); #else From c2e5ff7bd3f705a19dd39cd71dfa2a2b83d6b003 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 4 Dec 2018 22:25:57 +0900 Subject: [PATCH 136/396] Support utf8mb3 --- MySQLdb/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index aa9c7529..96b01528 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -300,7 +300,7 @@ def set_character_set(self, charset): set can only be changed in MySQL-4.1 and newer. If you try to change the character set from the current value in an older version, NotSupportedError will be raised.""" - if charset == "utf8mb4": + if charset in ("utf8mb4", "utf8mb3"): py_charset = "utf8" else: py_charset = charset From 41581254f8497a554f736bac805d02e5b06fda28 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 4 Dec 2018 21:31:33 -0600 Subject: [PATCH 137/396] Fix a reference leak in store_result and use_result if result object initialization fails. (#298) --- MySQLdb/_mysql.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index ce88d03c..66aba458 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1865,8 +1865,10 @@ _mysql_ConnectionObject_store_result( if (!kwarglist) goto error; r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); if (!r) goto error; - if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) + if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) { + Py_DECREF(r); goto error; + } result = (PyObject *) r; if (!(r->result)) { Py_DECREF(result); @@ -1924,9 +1926,11 @@ _mysql_ConnectionObject_use_result( if (!kwarglist) goto error; r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); if (!r) goto error; - result = (PyObject *) r; - if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) + if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) { + Py_DECREF(r); goto error; + } + result = (PyObject *) r; if (!(r->result)) { Py_DECREF(result); Py_INCREF(Py_None); From e30deabbc965b9f538bc4eef74ecb1700533f5c0 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 5 Dec 2018 18:44:51 +0900 Subject: [PATCH 138/396] doc: expand TABs --- doc/_mysql.rst | 2 +- doc/user_guide.rst | 196 ++++++++++++++++++++++----------------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/doc/_mysql.rst b/doc/_mysql.rst index 4a60591b..7ac4918d 100644 --- a/doc/_mysql.rst +++ b/doc/_mysql.rst @@ -4,4 +4,4 @@ _mysql Module .. automodule:: _mysql :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 55c2c83f..c83c3f3f 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -8,7 +8,7 @@ MySQLdb User's Guide Introduction ------------ -MySQLdb is a thread-compatible interface to the popular MySQL +MySQLdb is a interface to the popular MySQL database server that provides the Python database API. Installation @@ -28,7 +28,7 @@ for this module is intentionally weak because you probably should use the higher-level MySQLdb module. If you really need it, use the standard MySQL docs and transliterate as necessary. -.. _`MySQL documentation`: http://dev.mysql.com/doc/ +.. _`MySQL documentation`: https://dev.mysql.com/doc/ MySQL C API translation @@ -55,48 +55,48 @@ MySQL C API function mapping C API ``_mysql`` =================================== ================================== ``mysql_affected_rows()`` ``conn.affected_rows()`` - ``mysql_autocommit()`` ``conn.autocommit()`` - ``mysql_character_set_name()`` ``conn.character_set_name()`` - ``mysql_close()`` ``conn.close()`` - ``mysql_commit()`` ``conn.commit()`` - ``mysql_connect()`` ``_mysql.connect()`` - ``mysql_data_seek()`` ``result.data_seek()`` - ``mysql_debug()`` ``_mysql.debug()`` - ``mysql_dump_debug_info`` ``conn.dump_debug_info()`` - ``mysql_escape_string()`` ``_mysql.escape_string()`` - ``mysql_fetch_row()`` ``result.fetch_row()`` + ``mysql_autocommit()`` ``conn.autocommit()`` + ``mysql_character_set_name()`` ``conn.character_set_name()`` + ``mysql_close()`` ``conn.close()`` + ``mysql_commit()`` ``conn.commit()`` + ``mysql_connect()`` ``_mysql.connect()`` + ``mysql_data_seek()`` ``result.data_seek()`` + ``mysql_debug()`` ``_mysql.debug()`` + ``mysql_dump_debug_info`` ``conn.dump_debug_info()`` + ``mysql_escape_string()`` ``_mysql.escape_string()`` + ``mysql_fetch_row()`` ``result.fetch_row()`` ``mysql_get_character_set_info()`` ``conn.get_character_set_info()`` - ``mysql_get_client_info()`` ``_mysql.get_client_info()`` - ``mysql_get_host_info()`` ``conn.get_host_info()`` - ``mysql_get_proto_info()`` ``conn.get_proto_info()`` - ``mysql_get_server_info()`` ``conn.get_server_info()`` - ``mysql_info()`` ``conn.info()`` - ``mysql_insert_id()`` ``conn.insert_id()`` - ``mysql_num_fields()`` ``result.num_fields()`` - ``mysql_num_rows()`` ``result.num_rows()`` - ``mysql_options()`` various options to ``_mysql.connect()`` - ``mysql_ping()`` ``conn.ping()`` - ``mysql_query()`` ``conn.query()`` - ``mysql_real_connect()`` ``_mysql.connect()`` - ``mysql_real_query()`` ``conn.query()`` - ``mysql_real_escape_string()`` ``conn.escape_string()`` - ``mysql_rollback()`` ``conn.rollback()`` - ``mysql_row_seek()`` ``result.row_seek()`` - ``mysql_row_tell()`` ``result.row_tell()`` - ``mysql_select_db()`` ``conn.select_db()`` - ``mysql_set_character_set()`` ``conn.set_character_set()`` + ``mysql_get_client_info()`` ``_mysql.get_client_info()`` + ``mysql_get_host_info()`` ``conn.get_host_info()`` + ``mysql_get_proto_info()`` ``conn.get_proto_info()`` + ``mysql_get_server_info()`` ``conn.get_server_info()`` + ``mysql_info()`` ``conn.info()`` + ``mysql_insert_id()`` ``conn.insert_id()`` + ``mysql_num_fields()`` ``result.num_fields()`` + ``mysql_num_rows()`` ``result.num_rows()`` + ``mysql_options()`` various options to ``_mysql.connect()`` + ``mysql_ping()`` ``conn.ping()`` + ``mysql_query()`` ``conn.query()`` + ``mysql_real_connect()`` ``_mysql.connect()`` + ``mysql_real_query()`` ``conn.query()`` + ``mysql_real_escape_string()`` ``conn.escape_string()`` + ``mysql_rollback()`` ``conn.rollback()`` + ``mysql_row_seek()`` ``result.row_seek()`` + ``mysql_row_tell()`` ``result.row_tell()`` + ``mysql_select_db()`` ``conn.select_db()`` + ``mysql_set_character_set()`` ``conn.set_character_set()`` ``mysql_ssl_set()`` ``ssl`` option to ``_mysql.connect()`` - ``mysql_stat()`` ``conn.stat()`` - ``mysql_store_result()`` ``conn.store_result()`` - ``mysql_thread_id()`` ``conn.thread_id()`` - ``mysql_thread_safe_client()`` ``conn.thread_safe_client()`` - ``mysql_use_result()`` ``conn.use_result()`` - ``mysql_warning_count()`` ``conn.warning_count()`` - ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` - ``CR_*`` ``MySQLdb.constants.CR.*`` - ``ER_*`` ``MySQLdb.constants.ER.*`` - ``FIELD_TYPE_*`` ``MySQLdb.constants.FIELD_TYPE.*`` - ``FLAG_*`` ``MySQLdb.constants.FLAG.*`` + ``mysql_stat()`` ``conn.stat()`` + ``mysql_store_result()`` ``conn.store_result()`` + ``mysql_thread_id()`` ``conn.thread_id()`` + ``mysql_thread_safe_client()`` ``conn.thread_safe_client()`` + ``mysql_use_result()`` ``conn.use_result()`` + ``mysql_warning_count()`` ``conn.warning_count()`` + ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` + ``CR_*`` ``MySQLdb.constants.CR.*`` + ``ER_*`` ``MySQLdb.constants.ER.*`` + ``FIELD_TYPE_*`` ``MySQLdb.constants.FIELD_TYPE.*`` + ``FLAG_*`` ``MySQLdb.constants.FLAG.*`` =================================== ================================== @@ -126,7 +126,7 @@ We haven't even begun to touch upon all the parameters ``connect()`` can take. For this reason, I prefer to use keyword parameters:: db=_mysql.connect(host="localhost",user="joebob", - passwd="moonpie",db="thangs") + passwd="moonpie",db="thangs") This does exactly what the last example did, but is arguably easier to read. But since the default host is "localhost", and if your login @@ -260,19 +260,19 @@ Functions and attributes Only a few top-level functions and attributes are defined within MySQLdb. -connect(parameters...) - Constructor for creating a connection to the - database. Returns a Connection Object. Parameters are the - same as for the MySQL C API. In addition, there are a few - additional keywords that correspond to what you would pass - ``mysql_options()`` before connecting. Note that some - parameters must be specified as keyword arguments! The - default value for each parameter is NULL or zero, as - appropriate. Consult the MySQL documentation for more - details. The important parameters are: +connect(parameters...) + Constructor for creating a connection to the + database. Returns a Connection Object. Parameters are the + same as for the MySQL C API. In addition, there are a few + additional keywords that correspond to what you would pass + ``mysql_options()`` before connecting. Note that some + parameters must be specified as keyword arguments! The + default value for each parameter is NULL or zero, as + appropriate. Consult the MySQL documentation for more + details. The important parameters are: host - name of host to connect to. Default: use the local host + name of host to connect to. Default: use the local host via a UNIX socket (where applicable) user @@ -284,11 +284,11 @@ connect(parameters...) db database to use. Default: no default database. - port - TCP port of MySQL server. Default: standard port (3306). + port + TCP port of MySQL server. Default: standard port (3306). unix_socket - location of UNIX socket. Default: use default location or + location of UNIX socket. Default: use default location or TCP for remote hosts. conv @@ -331,45 +331,45 @@ connect(parameters...) connecting (MySQL-4.1 and later), you'll need to put the correct character set name in connection.charset. - If False, text-like columns are returned as normal strings, - but you can always write Unicode strings. + If False, text-like columns are returned as normal strings, + but you can always write Unicode strings. - *This must be a keyword parameter.* + *This must be a keyword parameter.* - charset - If present, the connection character set will be changed - to this character set, if they are not equal. Support for - changing the character set requires MySQL-4.1 and later - server; if the server is too old, UnsupportedError will be - raised. This option implies use_unicode=True, but you can - override this with use_unicode=False, though you probably - shouldn't. + charset + If present, the connection character set will be changed + to this character set, if they are not equal. Support for + changing the character set requires MySQL-4.1 and later + server; if the server is too old, UnsupportedError will be + raised. This option implies use_unicode=True, but you can + override this with use_unicode=False, though you probably + shouldn't. - If not present, the default character set is used. + If not present, the default character set is used. - *This must be a keyword parameter.* + *This must be a keyword parameter.* - sql_mode - If present, the session SQL mode will be set to the given - string. For more information on sql_mode, see the MySQL - documentation. Only available for 4.1 and newer servers. + sql_mode + If present, the session SQL mode will be set to the given + string. For more information on sql_mode, see the MySQL + documentation. Only available for 4.1 and newer servers. - If not present, the session SQL mode will be unchanged. + If not present, the session SQL mode will be unchanged. - *This must be a keyword parameter.* + *This must be a keyword parameter.* - ssl - This parameter takes a dictionary or mapping, where the - keys are parameter names used by the mysql_ssl_set_ MySQL - C API call. If this is set, it initiates an SSL connection - to the server; if there is no SSL support in the client, - an exception is raised. *This must be a keyword - parameter.* + ssl + This parameter takes a dictionary or mapping, where the + keys are parameter names used by the mysql_ssl_set_ MySQL + C API call. If this is set, it initiates an SSL connection + to the server; if there is no SSL support in the client, + an exception is raised. *This must be a keyword + parameter.* .. _mysql_ssl_set: http://dev.mysql.com/doc/refman/en/mysql-ssl-set.html -apilevel +apilevel String constant stating the supported DB API level. '2.0' threadsafety @@ -437,11 +437,11 @@ conv value), returning a Python value * a sequence of 2-tuples, where the first value is a combination - of flags from ``MySQLdb.constants.FLAG``, and the second value - is a function as above. The sequence is tested until the flags - on the field match those of the first value. If both values - are None, then the default conversion is done. Presently this - is only used to distinguish TEXT and BLOB columns. + of flags from ``MySQLdb.constants.FLAG``, and the second value + is a function as above. The sequence is tested until the flags + on the field match those of the first value. If both values + are None, then the default conversion is done. Presently this + is only used to distinguish TEXT and BLOB columns. If the key is a Python type or class, then the value is a callable Python object (usually a function) taking two arguments @@ -522,7 +522,7 @@ close() info() Returns some information about the last query. Normally - you don't need to check this. If there are any MySQL + you don't need to check this. If there are any MySQL warnings, it will cause a Warning to be issued through the Python warning module. By default, Warning causes a message to appear on the console. However, it is possible @@ -542,7 +542,7 @@ nextset() sets, it returns None; otherwise it returns a true value. Note that MySQL doesn't support multiple result sets until 4.1. - + Some examples ............. @@ -598,13 +598,13 @@ The only other method you are very likely to use is when you have to do a multi-row insert:: c.executemany( - """INSERT INTO breakfast (name, spam, eggs, sausage, price) - VALUES (%s, %s, %s, %s, %s)""", - [ - ("Spam and Sausage Lover's Plate", 5, 1, 8, 7.95 ), - ("Not So Much Spam Plate", 3, 2, 0, 3.95 ), - ("Don't Wany ANY SPAM! Plate", 0, 4, 3, 5.95 ) - ] ) + """INSERT INTO breakfast (name, spam, eggs, sausage, price) + VALUES (%s, %s, %s, %s, %s)""", + [ + ("Spam and Sausage Lover's Plate", 5, 1, 8, 7.95 ), + ("Not So Much Spam Plate", 3, 2, 0, 3.95 ), + ("Don't Wany ANY SPAM! Plate", 0, 4, 3, 5.95 ) + ] ) Here we are inserting three rows of five values. Notice that there is a mix of types (strings, ints, floats) though we still only use From 3bdcb2683288bebdfd6a9470ee5caf165c2e6113 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 5 Dec 2018 18:46:29 +0900 Subject: [PATCH 139/396] Expand TABs --- MySQLdb/constants/FIELD_TYPE.py | 3 +-- tests/dbapi20.py | 2 +- tests/test_MySQLdb_dbapi20.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/MySQLdb/constants/FIELD_TYPE.py b/MySQLdb/constants/FIELD_TYPE.py index 8a57b171..2a307644 100644 --- a/MySQLdb/constants/FIELD_TYPE.py +++ b/MySQLdb/constants/FIELD_TYPE.py @@ -2,7 +2,6 @@ These constants represent the various column (field) types that are supported by MySQL. - """ DECIMAL = 0 @@ -34,4 +33,4 @@ GEOMETRY = 255 CHAR = TINY -INTERVAL = ENUM +INTERVAL = ENUM diff --git a/tests/dbapi20.py b/tests/dbapi20.py index e28d5d1f..106ea405 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -704,7 +704,7 @@ def test_mixedfetch(self): def help_nextset_setUp(self,cur): ''' Should create a procedure called deleteme that returns two result sets, first the - number of rows in booze then "name from booze" + number of rows in booze then "name from booze" ''' raise NotImplementedError('Helper not implemented') #sql=""" diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 88eaaef8..85fc5d51 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -149,7 +149,7 @@ def test_callproc(self): def help_nextset_setUp(self,cur): ''' Should create a procedure called deleteme that returns two result sets, first the - number of rows in booze then "name from booze" + number of rows in booze then "name from booze" ''' sql=""" create procedure deleteme() From 27e40186e3d1642cca0f1585bd8e7f5d1f27a82a Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Wed, 5 Dec 2018 04:08:35 -0600 Subject: [PATCH 140/396] Resurrect Connection.fileno() (#300) This permits low-level access to the underlying network connection. For example, this can be used to tweak the TCP timeout (TCP_USER_TIMEOUT). --- MySQLdb/_mysql.c | 20 ++++++++++++++++++++ tests/test_MySQLdb_nonstandard.py | 3 +++ 2 files changed, 23 insertions(+) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 66aba458..81dc924f 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1639,6 +1639,20 @@ _mysql_ConnectionObject_field_count( return PyInt_FromLong((long)mysql_field_count(&(self->connection))); } +static char _mysql_ConnectionObject_fileno__doc__[] = +"Return file descriptor of the underlying libmysqlclient connection.\n\ +This provides raw access to the underlying network connection.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_fileno( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyInt_FromLong(self->connection.net.fd); +} + static char _mysql_ResultObject_num_fields__doc__[] = "Returns the number of fields (column) in the result." ; @@ -2133,6 +2147,12 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { METH_NOARGS, _mysql_ConnectionObject_field_count__doc__ }, + { + "fileno", + (PyCFunction)_mysql_ConnectionObject_fileno, + METH_NOARGS, + _mysql_ConnectionObject_fileno__doc__ + }, { "get_host_info", (PyCFunction)_mysql_ConnectionObject_get_host_info, diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index a06d5edf..e60a2a77 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -103,3 +103,6 @@ def test_client_flag(self): conn.client_flag = 0 conn.close() + + def test_fileno(self): + self.assertGreaterEqual(self.conn.fileno(), 0) From 2df4f2f8d821260dd6a7cd49d953e1a46fd50eb3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 5 Dec 2018 19:08:58 +0900 Subject: [PATCH 141/396] Update HISTORY --- HISTORY.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 2fd4500b..b4fed226 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -18,8 +18,7 @@ Release: TBD * Remove ``waiter`` option from Connection. -* Remove ``fileno``, ``escape_sequence``, and ``escape_dict`` methods - from Connection class. +* Remove ``escape_sequence``, and ``escape_dict`` methods from Connection class. * Remove automatic MySQL warning checking. From c754b2508023e19d6aba17cd8b9eaf0370cdff92 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 6 Dec 2018 02:39:25 +0900 Subject: [PATCH 142/396] Support field name other than UTF-8 (#301) --- MySQLdb/_mysql.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 81dc924f..b45682df 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1037,15 +1037,39 @@ _mysql_ResultObject_describe( { PyObject *d; MYSQL_FIELD *fields; + MY_CHARSET_INFO cs; + int isutf8 = 0; unsigned int i, n; + check_result_connection(self); + + mysql_get_character_set_info(&result_connection(self)->connection, &cs); + if (strncmp("utf8", cs.name, 4) == 0) { // utf8, utf8mb3, utf8mb4 + isutf8 = 1; + } + n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); if (!(d = PyTuple_New(n))) return NULL; for (i=0; i Date: Thu, 6 Dec 2018 19:33:31 +0900 Subject: [PATCH 143/396] Format --- MySQLdb/_mysql_exceptions.py | 13 - doc/FAQ.rst | 14 +- doc/MySQLdb.constants.rst | 118 +++--- doc/_mysql_exceptions.rst | 14 +- doc/modules.rst | 14 +- setup_windows.py | 1 - tests/capabilities.py | 596 ++++++++++++++--------------- tests/dbapi20.py | 32 +- tests/test_MySQLdb_capabilities.py | 372 +++++++++--------- tests/test_MySQLdb_dbapi20.py | 10 +- 10 files changed, 585 insertions(+), 599 deletions(-) diff --git a/MySQLdb/_mysql_exceptions.py b/MySQLdb/_mysql_exceptions.py index 74b765a7..99a79d77 100644 --- a/MySQLdb/_mysql_exceptions.py +++ b/MySQLdb/_mysql_exceptions.py @@ -13,42 +13,35 @@ class MySQLError(StandardError): - """Exception related to operation with MySQL.""" class Warning(Warning, MySQLError): - """Exception raised for important warnings like data truncations while inserting, etc.""" class Error(MySQLError): - """Exception that is the base class of all other error exceptions (not Warning).""" class InterfaceError(Error): - """Exception raised for errors that are related to the database interface rather than the database itself.""" class DatabaseError(Error): - """Exception raised for errors that are related to the database.""" class DataError(DatabaseError): - """Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range, etc.""" class OperationalError(DatabaseError): - """Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer, e.g. an unexpected disconnect occurs, the data source name is not @@ -57,31 +50,25 @@ class OperationalError(DatabaseError): class IntegrityError(DatabaseError): - """Exception raised when the relational integrity of the database is affected, e.g. a foreign key check fails, duplicate key, etc.""" class InternalError(DatabaseError): - """Exception raised when the database encounters an internal error, e.g. the cursor is not valid anymore, the transaction is out of sync, etc.""" class ProgrammingError(DatabaseError): - """Exception raised for programming errors, e.g. table not found or already exists, syntax error in the SQL statement, wrong number of parameters specified, etc.""" class NotSupportedError(DatabaseError): - """Exception raised in case a method or database API was used which is not supported by the database, e.g. requesting a .rollback() on a connection that does not support transaction or has transactions turned off.""" - - diff --git a/doc/FAQ.rst b/doc/FAQ.rst index 21e00b9b..14c8f72c 100644 --- a/doc/FAQ.rst +++ b/doc/FAQ.rst @@ -26,7 +26,7 @@ probably the issue, but it shouldn't happen any more. ImportError ----------- - ImportError: No module named _mysql + ImportError: No module named _mysql If you see this, it's likely you did some wrong when installing MySQLdb; re-read (or read) README. _mysql is the low-level C module @@ -42,7 +42,7 @@ still have to edit a configuration file so that the setup knows where to find MySQL and what libraries to include. - ImportError: libmysqlclient_r.so.14: cannot open shared object file: No such file or directory + ImportError: libmysqlclient_r.so.14: cannot open shared object file: No such file or directory The number after .so may vary, but this means you have a version of MySQLdb compiled against one version of MySQL, and are now trying to @@ -67,7 +67,7 @@ Solutions: `_. - ImportError: ld.so.1: python: fatal: libmtmalloc.so.1: DF_1_NOOPEN tagged object may not be dlopen()'ed + ImportError: ld.so.1: python: fatal: libmtmalloc.so.1: DF_1_NOOPEN tagged object may not be dlopen()'ed This is a weird one from Solaris. What does it mean? I have no idea. However, things like this can happen if there is some sort of a compiler @@ -79,9 +79,9 @@ different vendors. Solution: Rebuild Python or MySQL (or maybe both) from source. - ImportError: dlopen(./_mysql.so, 2): Symbol not found: _sprintf$LDBLStub - Referenced from: ./_mysql.so - Expected in: dynamic lookup + ImportError: dlopen(./_mysql.so, 2): Symbol not found: _sprintf$LDBLStub + Referenced from: ./_mysql.so + Expected in: dynamic lookup This is one from Mac OS X. It seems to have been a compiler mismatch, but this time between two different versions of GCC. It seems nearly @@ -110,7 +110,7 @@ rolled back, and they cause pending transactions to commit. Other Errors ------------ - OperationalError: (1251, 'Client does not support authentication protocol requested by server; consider upgrading MySQL client') + OperationalError: (1251, 'Client does not support authentication protocol requested by server; consider upgrading MySQL client') This means your server and client libraries are not the same version. More specifically, it probably means you have a 4.1 or newer server diff --git a/doc/MySQLdb.constants.rst b/doc/MySQLdb.constants.rst index e28dee2b..9276f8fa 100644 --- a/doc/MySQLdb.constants.rst +++ b/doc/MySQLdb.constants.rst @@ -1,59 +1,59 @@ -constants Package -================= - -:mod:`constants` Package ------------------------- - -.. automodule:: MySQLdb.constants - :members: - :undoc-members: - :show-inheritance: - -:mod:`CLIENT` Module --------------------- - -.. automodule:: MySQLdb.constants.CLIENT - :members: - :undoc-members: - :show-inheritance: - -:mod:`CR` Module ----------------- - -.. automodule:: MySQLdb.constants.CR - :members: - :undoc-members: - :show-inheritance: - -:mod:`ER` Module ----------------- - -.. automodule:: MySQLdb.constants.ER - :members: - :undoc-members: - :show-inheritance: - -:mod:`FIELD_TYPE` Module ------------------------- - -.. automodule:: MySQLdb.constants.FIELD_TYPE - :members: - :undoc-members: - :show-inheritance: - -:mod:`FLAG` Module ------------------- - -.. automodule:: MySQLdb.constants.FLAG - :members: - :undoc-members: - :show-inheritance: - -:mod:`REFRESH` Module ---------------------- - -.. automodule:: MySQLdb.constants.REFRESH - :members: - :undoc-members: - :show-inheritance: - +constants Package +================= + +:mod:`constants` Package +------------------------ + +.. automodule:: MySQLdb.constants + :members: + :undoc-members: + :show-inheritance: + +:mod:`CLIENT` Module +-------------------- + +.. automodule:: MySQLdb.constants.CLIENT + :members: + :undoc-members: + :show-inheritance: + +:mod:`CR` Module +---------------- + +.. automodule:: MySQLdb.constants.CR + :members: + :undoc-members: + :show-inheritance: + +:mod:`ER` Module +---------------- + +.. automodule:: MySQLdb.constants.ER + :members: + :undoc-members: + :show-inheritance: + +:mod:`FIELD_TYPE` Module +------------------------ + +.. automodule:: MySQLdb.constants.FIELD_TYPE + :members: + :undoc-members: + :show-inheritance: + +:mod:`FLAG` Module +------------------ + +.. automodule:: MySQLdb.constants.FLAG + :members: + :undoc-members: + :show-inheritance: + +:mod:`REFRESH` Module +--------------------- + +.. automodule:: MySQLdb.constants.REFRESH + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/_mysql_exceptions.rst b/doc/_mysql_exceptions.rst index 9b65de35..2d43525c 100644 --- a/doc/_mysql_exceptions.rst +++ b/doc/_mysql_exceptions.rst @@ -1,7 +1,7 @@ -_mysql_exceptions Module -======================== - -.. automodule:: _mysql_exceptions - :members: - :undoc-members: - :show-inheritance: +_mysql_exceptions Module +======================== + +.. automodule:: _mysql_exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/modules.rst b/doc/modules.rst index 7cf3faaa..998ac460 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -1,7 +1,7 @@ -MySQLdb -======= - -.. toctree:: - :maxdepth: 4 - - MySQLdb +MySQLdb +======= + +.. toctree:: + :maxdepth: 4 + + MySQLdb diff --git a/setup_windows.py b/setup_windows.py index 0811e127..5a4d236b 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -47,4 +47,3 @@ def get_config(): if __name__ == "__main__": sys.stderr.write("""You shouldn't be running this directly; it is used by setup.py.""") - diff --git a/tests/capabilities.py b/tests/capabilities.py index c341b3c9..5d913790 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -1,298 +1,298 @@ -#!/usr/bin/env python -O -""" Script to test database capabilities and the DB-API interface - for functionality and memory leaks. - - Adapted from a script by M-A Lemburg. - -""" -from time import time -import array -import unittest -from configdb import connection_factory - -from MySQLdb.compat import unichr - - -class DatabaseTest(unittest.TestCase): - - db_module = None - connect_args = () - connect_kwargs = dict() - create_table_extra = '' - rows = 10 - debug = False - - def setUp(self): - import gc - db = connection_factory(**self.connect_kwargs) - self.connection = db - self.cursor = db.cursor() - self.BLOBUText = u''.join([unichr(i) for i in range(16384)]) - self.BLOBBinary = self.db_module.Binary((u''.join([unichr(i) for i in range(256)] * 16)).encode('latin1')) - - leak_test = True - - def tearDown(self): - if self.leak_test: - import gc - del self.cursor - orphans = gc.collect() - self.failIf(orphans, "%d orphaned objects found after deleting cursor" % orphans) - - del self.connection - orphans = gc.collect() - self.failIf(orphans, "%d orphaned objects found after deleting connection" % orphans) - - def table_exists(self, name): - try: - self.cursor.execute('select * from %s where 1=0' % name) - except: - return False - else: - return True - - def quote_identifier(self, ident): - return '"%s"' % ident - - def new_table_name(self): - i = id(self.cursor) - while True: - name = self.quote_identifier('tb%08x' % i) - if not self.table_exists(name): - return name - i = i + 1 - - def create_table(self, columndefs): - - """ Create a table using a list of column definitions given in - columndefs. - - generator must be a function taking arguments (row_number, - col_number) returning a suitable data object for insertion - into the table. - - """ - self.table = self.new_table_name() - self.cursor.execute('CREATE TABLE %s (%s) %s' % - (self.table, - ',\n'.join(columndefs), - self.create_table_extra)) - - def check_data_integrity(self, columndefs, generator): - # insert - self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] - self.cursor.executemany(insert_statement, data) - self.connection.commit() - # verify - self.cursor.execute('select * from %s' % self.table) - l = self.cursor.fetchall() - self.assertEqual(len(l), self.rows) - try: - for i in range(self.rows): - for j in range(len(columndefs)): - self.assertEqual(l[i][j], generator(i,j)) - finally: - if not self.debug: - self.cursor.execute('drop table %s' % (self.table)) - - def test_transactions(self): - columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') - def generator(row, col): - if col == 0: return row - else: return ('%i' % (row%10))*255 - self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] - self.cursor.executemany(insert_statement, data) - # verify - self.connection.commit() - self.cursor.execute('select * from %s' % self.table) - l = self.cursor.fetchall() - self.assertEqual(len(l), self.rows) - for i in range(self.rows): - for j in range(len(columndefs)): - self.assertEqual(l[i][j], generator(i,j)) - delete_statement = 'delete from %s where col1=%%s' % self.table - self.cursor.execute(delete_statement, (0,)) - self.cursor.execute('select col1 from %s where col1=%s' % \ - (self.table, 0)) - l = self.cursor.fetchall() - self.assertFalse(l, "DELETE didn't work") - self.connection.rollback() - self.cursor.execute('select col1 from %s where col1=%s' % \ - (self.table, 0)) - l = self.cursor.fetchall() - self.assertTrue(len(l) == 1, "ROLLBACK didn't work") - self.cursor.execute('drop table %s' % (self.table)) - - def test_truncation(self): - columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') - def generator(row, col): - if col == 0: return row - else: return ('%i' % (row%10))*((255-self.rows//2)+row) - self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - - try: - self.cursor.execute(insert_statement, (0, '0'*256)) - except self.connection.DataError: - pass - else: - self.fail("Over-long column did not generate warnings/exception with single insert") - - self.connection.rollback() - - try: - for i in range(self.rows): - data = [] - for j in range(len(columndefs)): - data.append(generator(i,j)) - self.cursor.execute(insert_statement,tuple(data)) - except self.connection.DataError: - pass - else: - self.fail("Over-long columns did not generate warnings/exception with execute()") - - self.connection.rollback() - - try: - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] - self.cursor.executemany(insert_statement, data) - except self.connection.DataError: - pass - else: - self.fail("Over-long columns did not generate warnings/exception with executemany()") - - self.connection.rollback() - self.cursor.execute('drop table %s' % (self.table)) - - def test_CHAR(self): - # Character data - def generator(row,col): - return ('%i' % ((row+col) % 10)) * 255 - self.check_data_integrity( - ('col1 char(255)','col2 char(255)'), - generator) - - def test_INT(self): - # Number data - def generator(row,col): - return row*row - self.check_data_integrity( - ('col1 INT',), - generator) - - def test_DECIMAL(self): - # DECIMAL - from decimal import Decimal - def generator(row,col): - return Decimal("%d.%02d" % (row, col)) - self.check_data_integrity( - ('col1 DECIMAL(5,2)',), - generator) - - val = Decimal('1.11111111111111119E-7') - self.cursor.execute('SELECT %s', (val,)) - result = self.cursor.fetchone()[0] - self.assertEqual(result, val) - self.assertIsInstance(result, Decimal) - - self.cursor.execute('SELECT %s + %s', (Decimal('0.1'), Decimal('0.2'))) - result = self.cursor.fetchone()[0] - self.assertEqual(result, Decimal('0.3')) - self.assertIsInstance(result, Decimal) - - def test_DATE(self): - ticks = time() - def generator(row,col): - return self.db_module.DateFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 DATE',), - generator) - - def test_TIME(self): - ticks = time() - def generator(row,col): - return self.db_module.TimeFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 TIME',), - generator) - - def test_DATETIME(self): - ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 DATETIME',), - generator) - - def test_TIMESTAMP(self): - ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 TIMESTAMP',), - generator) - - def test_fractional_TIMESTAMP(self): - ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313+row*0.7*col/3.0) - self.check_data_integrity( - ('col1 TIMESTAMP',), - generator) - - def test_LONG(self): - def generator(row,col): - if col == 0: - return row - else: - return self.BLOBUText # 'BLOB Text ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 LONG'), - generator) - - def test_TEXT(self): - def generator(row,col): - return self.BLOBUText # 'BLOB Text ' * 1024 - self.check_data_integrity( - ('col2 TEXT',), - generator) - - def test_LONG_BYTE(self): - def generator(row,col): - if col == 0: - return row - else: - return self.BLOBBinary # 'BLOB\000Binary ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 LONG BYTE'), - generator) - - def test_BLOB(self): - def generator(row,col): - if col == 0: - return row - else: - return self.BLOBBinary # 'BLOB\000Binary ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 BLOB'), - generator) - - def test_DOUBLE(self): - for val in (18014398509481982.0, 0.1): - self.cursor.execute('SELECT %s', (val,)); - result = self.cursor.fetchone()[0] - self.assertEqual(result, val) - self.assertIsInstance(result, float) +#!/usr/bin/env python -O +""" Script to test database capabilities and the DB-API interface + for functionality and memory leaks. + + Adapted from a script by M-A Lemburg. + +""" +from time import time +import array +import unittest +from configdb import connection_factory + +from MySQLdb.compat import unichr + + +class DatabaseTest(unittest.TestCase): + + db_module = None + connect_args = () + connect_kwargs = dict() + create_table_extra = '' + rows = 10 + debug = False + + def setUp(self): + import gc + db = connection_factory(**self.connect_kwargs) + self.connection = db + self.cursor = db.cursor() + self.BLOBUText = u''.join([unichr(i) for i in range(16384)]) + self.BLOBBinary = self.db_module.Binary((u''.join([unichr(i) for i in range(256)] * 16)).encode('latin1')) + + leak_test = True + + def tearDown(self): + if self.leak_test: + import gc + del self.cursor + orphans = gc.collect() + self.failIf(orphans, "%d orphaned objects found after deleting cursor" % orphans) + + del self.connection + orphans = gc.collect() + self.failIf(orphans, "%d orphaned objects found after deleting connection" % orphans) + + def table_exists(self, name): + try: + self.cursor.execute('select * from %s where 1=0' % name) + except: + return False + else: + return True + + def quote_identifier(self, ident): + return '"%s"' % ident + + def new_table_name(self): + i = id(self.cursor) + while True: + name = self.quote_identifier('tb%08x' % i) + if not self.table_exists(name): + return name + i = i + 1 + + def create_table(self, columndefs): + + """ Create a table using a list of column definitions given in + columndefs. + + generator must be a function taking arguments (row_number, + col_number) returning a suitable data object for insertion + into the table. + + """ + self.table = self.new_table_name() + self.cursor.execute('CREATE TABLE %s (%s) %s' % + (self.table, + ',\n'.join(columndefs), + self.create_table_extra)) + + def check_data_integrity(self, columndefs, generator): + # insert + self.create_table(columndefs) + insert_statement = ('INSERT INTO %s VALUES (%s)' % + (self.table, + ','.join(['%s'] * len(columndefs)))) + data = [ [ generator(i,j) for j in range(len(columndefs)) ] + for i in range(self.rows) ] + self.cursor.executemany(insert_statement, data) + self.connection.commit() + # verify + self.cursor.execute('select * from %s' % self.table) + l = self.cursor.fetchall() + self.assertEqual(len(l), self.rows) + try: + for i in range(self.rows): + for j in range(len(columndefs)): + self.assertEqual(l[i][j], generator(i,j)) + finally: + if not self.debug: + self.cursor.execute('drop table %s' % (self.table)) + + def test_transactions(self): + columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') + def generator(row, col): + if col == 0: return row + else: return ('%i' % (row%10))*255 + self.create_table(columndefs) + insert_statement = ('INSERT INTO %s VALUES (%s)' % + (self.table, + ','.join(['%s'] * len(columndefs)))) + data = [ [ generator(i,j) for j in range(len(columndefs)) ] + for i in range(self.rows) ] + self.cursor.executemany(insert_statement, data) + # verify + self.connection.commit() + self.cursor.execute('select * from %s' % self.table) + l = self.cursor.fetchall() + self.assertEqual(len(l), self.rows) + for i in range(self.rows): + for j in range(len(columndefs)): + self.assertEqual(l[i][j], generator(i,j)) + delete_statement = 'delete from %s where col1=%%s' % self.table + self.cursor.execute(delete_statement, (0,)) + self.cursor.execute('select col1 from %s where col1=%s' % \ + (self.table, 0)) + l = self.cursor.fetchall() + self.assertFalse(l, "DELETE didn't work") + self.connection.rollback() + self.cursor.execute('select col1 from %s where col1=%s' % \ + (self.table, 0)) + l = self.cursor.fetchall() + self.assertTrue(len(l) == 1, "ROLLBACK didn't work") + self.cursor.execute('drop table %s' % (self.table)) + + def test_truncation(self): + columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') + def generator(row, col): + if col == 0: return row + else: return ('%i' % (row%10))*((255-self.rows//2)+row) + self.create_table(columndefs) + insert_statement = ('INSERT INTO %s VALUES (%s)' % + (self.table, + ','.join(['%s'] * len(columndefs)))) + + try: + self.cursor.execute(insert_statement, (0, '0'*256)) + except self.connection.DataError: + pass + else: + self.fail("Over-long column did not generate warnings/exception with single insert") + + self.connection.rollback() + + try: + for i in range(self.rows): + data = [] + for j in range(len(columndefs)): + data.append(generator(i,j)) + self.cursor.execute(insert_statement,tuple(data)) + except self.connection.DataError: + pass + else: + self.fail("Over-long columns did not generate warnings/exception with execute()") + + self.connection.rollback() + + try: + data = [ [ generator(i,j) for j in range(len(columndefs)) ] + for i in range(self.rows) ] + self.cursor.executemany(insert_statement, data) + except self.connection.DataError: + pass + else: + self.fail("Over-long columns did not generate warnings/exception with executemany()") + + self.connection.rollback() + self.cursor.execute('drop table %s' % (self.table)) + + def test_CHAR(self): + # Character data + def generator(row,col): + return ('%i' % ((row+col) % 10)) * 255 + self.check_data_integrity( + ('col1 char(255)','col2 char(255)'), + generator) + + def test_INT(self): + # Number data + def generator(row,col): + return row*row + self.check_data_integrity( + ('col1 INT',), + generator) + + def test_DECIMAL(self): + # DECIMAL + from decimal import Decimal + def generator(row,col): + return Decimal("%d.%02d" % (row, col)) + self.check_data_integrity( + ('col1 DECIMAL(5,2)',), + generator) + + val = Decimal('1.11111111111111119E-7') + self.cursor.execute('SELECT %s', (val,)) + result = self.cursor.fetchone()[0] + self.assertEqual(result, val) + self.assertIsInstance(result, Decimal) + + self.cursor.execute('SELECT %s + %s', (Decimal('0.1'), Decimal('0.2'))) + result = self.cursor.fetchone()[0] + self.assertEqual(result, Decimal('0.3')) + self.assertIsInstance(result, Decimal) + + def test_DATE(self): + ticks = time() + def generator(row,col): + return self.db_module.DateFromTicks(ticks+row*86400-col*1313) + self.check_data_integrity( + ('col1 DATE',), + generator) + + def test_TIME(self): + ticks = time() + def generator(row,col): + return self.db_module.TimeFromTicks(ticks+row*86400-col*1313) + self.check_data_integrity( + ('col1 TIME',), + generator) + + def test_DATETIME(self): + ticks = time() + def generator(row,col): + return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) + self.check_data_integrity( + ('col1 DATETIME',), + generator) + + def test_TIMESTAMP(self): + ticks = time() + def generator(row,col): + return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) + self.check_data_integrity( + ('col1 TIMESTAMP',), + generator) + + def test_fractional_TIMESTAMP(self): + ticks = time() + def generator(row,col): + return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313+row*0.7*col/3.0) + self.check_data_integrity( + ('col1 TIMESTAMP',), + generator) + + def test_LONG(self): + def generator(row,col): + if col == 0: + return row + else: + return self.BLOBUText # 'BLOB Text ' * 1024 + self.check_data_integrity( + ('col1 INT','col2 LONG'), + generator) + + def test_TEXT(self): + def generator(row,col): + return self.BLOBUText # 'BLOB Text ' * 1024 + self.check_data_integrity( + ('col2 TEXT',), + generator) + + def test_LONG_BYTE(self): + def generator(row,col): + if col == 0: + return row + else: + return self.BLOBBinary # 'BLOB\000Binary ' * 1024 + self.check_data_integrity( + ('col1 INT','col2 LONG BYTE'), + generator) + + def test_BLOB(self): + def generator(row,col): + if col == 0: + return row + else: + return self.BLOBBinary # 'BLOB\000Binary ' * 1024 + self.check_data_integrity( + ('col1 INT','col2 BLOB'), + generator) + + def test_DOUBLE(self): + for val in (18014398509481982.0, 0.1): + self.cursor.execute('SELECT %s', (val,)); + result = self.cursor.fetchone()[0] + self.assertEqual(result, val) + self.assertIsInstance(result, float) diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 106ea405..79c188a5 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -''' Python DB API 2.0 driver compliance unit test suite. - +''' Python DB API 2.0 driver compliance unit test suite. + This software is Public Domain and may be used without restrictions. "Now we have booze and barflies entering the discussion, plus rumours of @@ -67,8 +67,8 @@ class DatabaseAPI20Test(unittest.TestCase): ''' Test a database self.driver for DB API 2.0 compatibility. This implementation tests Gadfly, but the TestCase - is structured so that other self.drivers can subclass this - test case to ensure compiliance with the DB-API. It is + is structured so that other self.drivers can subclass this + test case to ensure compiliance with the DB-API. It is expected that this TestCase may be expanded in the future if ambiguities or edge conditions are discovered. @@ -78,9 +78,9 @@ class DatabaseAPI20Test(unittest.TestCase): self.driver, connect_args and connect_kw_args. Class specification should be as follows: - import dbapi20 + import dbapi20 class mytest(dbapi20.DatabaseAPI20Test): - [...] + [...] Don't 'import DatabaseAPI20Test from dbapi20', or you will confuse the unit tester - just 'import dbapi20'. @@ -99,7 +99,7 @@ class mytest(dbapi20.DatabaseAPI20Test): xddl2 = 'drop table %sbarflys' % table_prefix lowerfunc = 'lower' # Name of stored procedure to convert string->lowercase - + # Some drivers may need to override these helpers, for example adding # a 'commit' after the execute. def executeDDL1(self,cursor): @@ -123,10 +123,10 @@ def tearDown(self): try: cur = con.cursor() for ddl in (self.xddl1,self.xddl2): - try: + try: cur.execute(ddl) con.commit() - except self.driver.Error: + except self.driver.Error: # Assume table didn't exist. Other tests will check if # execute is busted. pass @@ -238,7 +238,7 @@ def test_rollback(self): con.rollback() except self.driver.NotSupportedError: pass - + def test_cursor(self): con = self._connect() try: @@ -392,7 +392,7 @@ def _paraminsert(self,cur): ) elif self.driver.paramstyle == 'named': cur.execute( - 'insert into %sbooze values (:beer)' % self.table_prefix, + 'insert into %sbooze values (:beer)' % self.table_prefix, {'beer':"Cooper's"} ) elif self.driver.paramstyle == 'format': @@ -532,7 +532,7 @@ def _populate(self): tests. ''' populate = [ - "insert into %sbooze values ('%s')" % (self.table_prefix,s) + "insert into %sbooze values ('%s')" % (self.table_prefix,s) for s in self.samples ] return populate @@ -593,7 +593,7 @@ def test_fetchmany(self): self.assertEqual(len(rows),6) rows = [r[0] for r in rows] rows.sort() - + # Make sure we get the right data back out for i in range(0,6): self.assertEqual(rows[i],self.samples[i], @@ -664,10 +664,10 @@ def test_fetchall(self): 'cursor.fetchall should return an empty list if ' 'a select query returns no rows' ) - + finally: con.close() - + def test_mixedfetch(self): con = self._connect() try: @@ -703,7 +703,7 @@ def test_mixedfetch(self): def help_nextset_setUp(self,cur): ''' Should create a procedure called deleteme - that returns two result sets, first the + that returns two result sets, first the number of rows in booze then "name from booze" ''' raise NotImplementedError('Helper not implemented') diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index e9b0e2a9..6e39d146 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -1,186 +1,186 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import capabilities -from datetime import timedelta -from contextlib import closing -import unittest -import MySQLdb -from MySQLdb.compat import unicode -from MySQLdb import cursors -from configdb import connection_factory -import warnings - - -warnings.filterwarnings('ignore') - - -class test_MySQLdb(capabilities.DatabaseTest): - - db_module = MySQLdb - connect_args = () - connect_kwargs = dict(use_unicode=True, sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL") - create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8" - leak_test = False - - def quote_identifier(self, ident): - return "`%s`" % ident - - def test_TIME(self): - def generator(row,col): - return timedelta(0, row*8000) - self.check_data_integrity( - ('col1 TIME',), - generator) - - def test_TINYINT(self): - # Number data - def generator(row, col): - v = (row*row) % 256 - if v > 127: - v = v-256 - return v - self.check_data_integrity( - ('col1 TINYINT',), - generator) - - def test_stored_procedures(self): - db = self.connection - c = self.cursor - self.create_table(('pos INT', 'tree CHAR(20)')) - c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, - list(enumerate('ash birch cedar Lärche pine'.split()))) - db.commit() - - c.execute(""" - CREATE PROCEDURE test_sp(IN t VARCHAR(255)) - BEGIN - SELECT pos FROM %s WHERE tree = t; - END - """ % self.table) - db.commit() - - c.callproc('test_sp', ('Lärche',)) - rows = c.fetchall() - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0][0], 3) - c.nextset() - - c.execute("DROP PROCEDURE test_sp") - c.execute('drop table %s' % (self.table)) - - def test_small_CHAR(self): - # Character data - def generator(row,col): - i = (row*col+62)%256 - if i == 62: return '' - if i == 63: return None - return chr(i) - self.check_data_integrity( - ('col1 char(1)','col2 char(1)'), - generator) - - def test_BIT(self): - c = self.cursor - try: - c.execute("""create table test_BIT ( - b3 BIT(3), - b7 BIT(10), - b64 BIT(64))""") - - one64 = '1'*64 - c.execute( - "insert into test_BIT (b3, b7, b64)" - " VALUES (b'011', b'1111111111', b'%s')" - % one64) - - c.execute("SELECT b3, b7, b64 FROM test_BIT") - row = c.fetchone() - self.assertEqual(row[0], b'\x03') - self.assertEqual(row[1], b'\x03\xff') - self.assertEqual(row[2], b'\xff'*8) - finally: - c.execute("drop table if exists test_BIT") - - def test_MULTIPOLYGON(self): - c = self.cursor - try: - c.execute("""create table test_MULTIPOLYGON ( - id INTEGER PRIMARY KEY, - border MULTIPOLYGON)""") - - c.execute( - "insert into test_MULTIPOLYGON (id, border)" - " VALUES (1, GeomFromText('MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))'))" - ) - - c.execute("SELECT id, AsText(border) FROM test_MULTIPOLYGON") - row = c.fetchone() - self.assertEqual(row[0], 1) - self.assertEqual(row[1], 'MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))') - - c.execute("SELECT id, AsWKB(border) FROM test_MULTIPOLYGON") - row = c.fetchone() - self.assertEqual(row[0], 1) - self.assertNotEqual(len(row[1]), 0) - - c.execute("SELECT id, border FROM test_MULTIPOLYGON") - row = c.fetchone() - self.assertEqual(row[0], 1) - self.assertNotEqual(len(row[1]), 0) - finally: - c.execute("drop table if exists test_MULTIPOLYGON") - - def test_bug_2671682(self): - from MySQLdb.constants import ER - try: - self.cursor.execute("describe some_non_existent_table"); - except self.connection.ProgrammingError as msg: - self.assertTrue(str(ER.NO_SUCH_TABLE) in str(msg)) - - def test_bug_3514287(self): - c = self.cursor - try: - c.execute("""create table bug_3541287 ( - c1 CHAR(10), - t1 TIMESTAMP)""") - c.execute("insert into bug_3541287 (c1,t1) values (%s, NOW())", - ("blah",)) - finally: - c.execute("drop table if exists bug_3541287") - - def test_ping(self): - self.connection.ping() - - def test_reraise_exception(self): - c = self.cursor - try: - c.execute("SELECT x FROM not_existing_table") - except MySQLdb.ProgrammingError as e: - self.assertEqual(e.args[0], 1146) - return - self.fail("Should raise ProgrammingError") - - def test_binary_prefix(self): - # verify prefix behaviour when enabled, disabled and for default (disabled) - for binary_prefix in (True, False, None): - kwargs = self.connect_kwargs.copy() - # needs to be set to can guarantee CHARSET response for normal strings - kwargs['charset'] = 'utf8' - if binary_prefix != None: - kwargs['binary_prefix'] = binary_prefix - - with closing(connection_factory(**kwargs)) as conn: - with closing(conn.cursor()) as c: - c.execute('SELECT CHARSET(%s)', (MySQLdb.Binary(b'raw bytes'),)) - self.assertEqual(c.fetchall()[0][0], 'binary' if binary_prefix else 'utf8') - # normal strings should not get prefix - c.execute('SELECT CHARSET(%s)', ('str',)) - self.assertEqual(c.fetchall()[0][0], 'utf8') - - -if __name__ == '__main__': - if test_MySQLdb.leak_test: - import gc - gc.enable() - gc.set_debug(gc.DEBUG_LEAK) - unittest.main() +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import capabilities +from datetime import timedelta +from contextlib import closing +import unittest +import MySQLdb +from MySQLdb.compat import unicode +from MySQLdb import cursors +from configdb import connection_factory +import warnings + + +warnings.filterwarnings('ignore') + + +class test_MySQLdb(capabilities.DatabaseTest): + + db_module = MySQLdb + connect_args = () + connect_kwargs = dict(use_unicode=True, sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL") + create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8" + leak_test = False + + def quote_identifier(self, ident): + return "`%s`" % ident + + def test_TIME(self): + def generator(row,col): + return timedelta(0, row*8000) + self.check_data_integrity( + ('col1 TIME',), + generator) + + def test_TINYINT(self): + # Number data + def generator(row, col): + v = (row*row) % 256 + if v > 127: + v = v-256 + return v + self.check_data_integrity( + ('col1 TINYINT',), + generator) + + def test_stored_procedures(self): + db = self.connection + c = self.cursor + self.create_table(('pos INT', 'tree CHAR(20)')) + c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, + list(enumerate('ash birch cedar Lärche pine'.split()))) + db.commit() + + c.execute(""" + CREATE PROCEDURE test_sp(IN t VARCHAR(255)) + BEGIN + SELECT pos FROM %s WHERE tree = t; + END + """ % self.table) + db.commit() + + c.callproc('test_sp', ('Lärche',)) + rows = c.fetchall() + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0][0], 3) + c.nextset() + + c.execute("DROP PROCEDURE test_sp") + c.execute('drop table %s' % (self.table)) + + def test_small_CHAR(self): + # Character data + def generator(row,col): + i = (row*col+62)%256 + if i == 62: return '' + if i == 63: return None + return chr(i) + self.check_data_integrity( + ('col1 char(1)','col2 char(1)'), + generator) + + def test_BIT(self): + c = self.cursor + try: + c.execute("""create table test_BIT ( + b3 BIT(3), + b7 BIT(10), + b64 BIT(64))""") + + one64 = '1'*64 + c.execute( + "insert into test_BIT (b3, b7, b64)" + " VALUES (b'011', b'1111111111', b'%s')" + % one64) + + c.execute("SELECT b3, b7, b64 FROM test_BIT") + row = c.fetchone() + self.assertEqual(row[0], b'\x03') + self.assertEqual(row[1], b'\x03\xff') + self.assertEqual(row[2], b'\xff'*8) + finally: + c.execute("drop table if exists test_BIT") + + def test_MULTIPOLYGON(self): + c = self.cursor + try: + c.execute("""create table test_MULTIPOLYGON ( + id INTEGER PRIMARY KEY, + border MULTIPOLYGON)""") + + c.execute( + "insert into test_MULTIPOLYGON (id, border)" + " VALUES (1, GeomFromText('MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))'))" + ) + + c.execute("SELECT id, AsText(border) FROM test_MULTIPOLYGON") + row = c.fetchone() + self.assertEqual(row[0], 1) + self.assertEqual(row[1], 'MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))') + + c.execute("SELECT id, AsWKB(border) FROM test_MULTIPOLYGON") + row = c.fetchone() + self.assertEqual(row[0], 1) + self.assertNotEqual(len(row[1]), 0) + + c.execute("SELECT id, border FROM test_MULTIPOLYGON") + row = c.fetchone() + self.assertEqual(row[0], 1) + self.assertNotEqual(len(row[1]), 0) + finally: + c.execute("drop table if exists test_MULTIPOLYGON") + + def test_bug_2671682(self): + from MySQLdb.constants import ER + try: + self.cursor.execute("describe some_non_existent_table"); + except self.connection.ProgrammingError as msg: + self.assertTrue(str(ER.NO_SUCH_TABLE) in str(msg)) + + def test_bug_3514287(self): + c = self.cursor + try: + c.execute("""create table bug_3541287 ( + c1 CHAR(10), + t1 TIMESTAMP)""") + c.execute("insert into bug_3541287 (c1,t1) values (%s, NOW())", + ("blah",)) + finally: + c.execute("drop table if exists bug_3541287") + + def test_ping(self): + self.connection.ping() + + def test_reraise_exception(self): + c = self.cursor + try: + c.execute("SELECT x FROM not_existing_table") + except MySQLdb.ProgrammingError as e: + self.assertEqual(e.args[0], 1146) + return + self.fail("Should raise ProgrammingError") + + def test_binary_prefix(self): + # verify prefix behaviour when enabled, disabled and for default (disabled) + for binary_prefix in (True, False, None): + kwargs = self.connect_kwargs.copy() + # needs to be set to can guarantee CHARSET response for normal strings + kwargs['charset'] = 'utf8' + if binary_prefix != None: + kwargs['binary_prefix'] = binary_prefix + + with closing(connection_factory(**kwargs)) as conn: + with closing(conn.cursor()) as c: + c.execute('SELECT CHARSET(%s)', (MySQLdb.Binary(b'raw bytes'),)) + self.assertEqual(c.fetchall()[0][0], 'binary' if binary_prefix else 'utf8') + # normal strings should not get prefix + c.execute('SELECT CHARSET(%s)', ('str',)) + self.assertEqual(c.fetchall()[0][0], 'utf8') + + +if __name__ == '__main__': + if test_MySQLdb.leak_test: + import gc + gc.enable() + gc.set_debug(gc.DEBUG_LEAK) + unittest.main() diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 85fc5d51..1e808bd1 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -20,7 +20,7 @@ def test_nextset(self): pass test for an exception if the statement cannot return a result set. MySQL always returns a result set; it's just that some things return empty result sets.""" - + def test_fetchall(self): con = self._connect() try: @@ -66,10 +66,10 @@ def test_fetchall(self): 'cursor.fetchall should return an empty list if ' 'a select query returns no rows' ) - + finally: con.close() - + def test_fetchone(self): con = self._connect() try: @@ -148,7 +148,7 @@ def test_callproc(self): def help_nextset_setUp(self,cur): ''' Should create a procedure called deleteme - that returns two result sets, first the + that returns two result sets, first the number of rows in booze then "name from booze" ''' sql=""" @@ -200,6 +200,6 @@ def test_nextset(self): finally: con.close() - + if __name__ == '__main__': unittest.main() From 509b6ce83713b32f91203e6148330f57b152f607 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 7 Dec 2018 02:09:22 +0900 Subject: [PATCH 144/396] Document removal of _last_executed Fixes #303 --- HISTORY.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index b4fed226..4691fceb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -42,6 +42,11 @@ Release: 2018-12-04 * Fix SSCursor may raise same exception twice (#282) + * This removed ``Cursor._last_executed`` which is duplicate of ``Cursor._executed``. + Both member are private. So this type of change is not documented generally. + But Django used the private member for ``last_executed_query`` implementation. + If you use the method, you shouldn't upgrade mysqlclient to this version. + * ``waiter`` option is now deprecated. (#285) * Fixed SSL support is not detected when built with MySQL < 5.1 (#291) From 628bb1b433f9bb2205964d13e0142aa25e777905 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 7 Dec 2018 02:11:51 +0900 Subject: [PATCH 145/396] Refine last commit --- HISTORY.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4691fceb..1a5fd824 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -42,10 +42,11 @@ Release: 2018-12-04 * Fix SSCursor may raise same exception twice (#282) - * This removed ``Cursor._last_executed`` which is duplicate of ``Cursor._executed``. - Both member are private. So this type of change is not documented generally. - But Django used the private member for ``last_executed_query`` implementation. - If you use the method, you shouldn't upgrade mysqlclient to this version. + * This removed ``Cursor._last_executed`` which was duplicate of ``Cursor._executed``. + Both members are private. So this type of changes are not documented in changelog + generally. But Django used the private member for ``last_executed_query`` implementation. + If you use the method the method directly or indirectly, this version will break + your application. See https://code.djangoproject.com/ticket/30013 * ``waiter`` option is now deprecated. (#285) From 5e8eeac47f511ca63d6b40dcc3a47190d96a0c0d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 7 Dec 2018 02:39:33 +0900 Subject: [PATCH 146/396] Stop using surrogate escape (#302) It was workaround for `bytes %`. Since we dropped Python 3.4 support, we can use just `bytes %` now. --- MySQLdb/_mysql.c | 35 +++++++--------- MySQLdb/connections.py | 48 +++++----------------- MySQLdb/converters.py | 4 +- MySQLdb/cursors.py | 90 +++++++++++++----------------------------- MySQLdb/times.py | 4 +- 5 files changed, 56 insertions(+), 125 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index b45682df..6c12b923 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -915,14 +915,15 @@ _mysql.string_literal(obj) cannot handle character sets."; static PyObject * _mysql_string_literal( _mysql_ConnectionObject *self, - PyObject *args) + PyObject *o) { - PyObject *str, *s, *o, *d; + PyObject *str, *s; char *in, *out; int len, size; + if (self && PyModule_Check((PyObject*)self)) self = NULL; - if (!PyArg_ParseTuple(args, "O|O:string_literal", &o, &d)) return NULL; + if (PyBytes_Check(o)) { s = o; Py_INCREF(s); @@ -965,33 +966,25 @@ static PyObject *_mysql_NULL; static PyObject * _escape_item( + PyObject *self, PyObject *item, PyObject *d) { PyObject *quoted=NULL, *itemtype, *itemconv; - if (!(itemtype = PyObject_Type(item))) - goto error; + if (!(itemtype = PyObject_Type(item))) { + return NULL; + } itemconv = PyObject_GetItem(d, itemtype); Py_DECREF(itemtype); if (!itemconv) { PyErr_Clear(); - itemconv = PyObject_GetItem(d, -#ifdef IS_PY3K - (PyObject *) &PyUnicode_Type); -#else - (PyObject *) &PyString_Type); -#endif - } - if (!itemconv) { - PyErr_SetString(PyExc_TypeError, - "no default type converter defined"); - goto error; + return _mysql_string_literal((_mysql_ConnectionObject*)self, item); } Py_INCREF(d); quoted = PyObject_CallFunction(itemconv, "OO", item, d); Py_DECREF(d); Py_DECREF(itemconv); -error: + return quoted; } @@ -1013,14 +1006,14 @@ _mysql_escape( "argument 2 must be a mapping"); return NULL; } - return _escape_item(o, d); + return _escape_item(self, o, d); } else { if (!self) { PyErr_SetString(PyExc_TypeError, "argument 2 must be a mapping"); return NULL; } - return _escape_item(o, + return _escape_item(self, o, ((_mysql_ConnectionObject *) self)->converter); } } @@ -2264,7 +2257,7 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { { "string_literal", (PyCFunction)_mysql_string_literal, - METH_VARARGS, + METH_O, _mysql_string_literal__doc__}, { "thread_id", @@ -2587,7 +2580,7 @@ _mysql_methods[] = { { "string_literal", (PyCFunction)_mysql_string_literal, - METH_VARARGS, + METH_O, _mysql_string_literal__doc__ }, { diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 96b01528..27a8d437 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -16,18 +16,6 @@ ) -if not PY2: - if sys.version_info[:2] < (3, 6): - # See http://bugs.python.org/issue24870 - _surrogateescape_table = [chr(i) if i < 0x80 else chr(i + 0xdc00) for i in range(256)] - - def _fast_surrogateescape(s): - return s.decode('latin1').translate(_surrogateescape_table) - else: - def _fast_surrogateescape(s): - return s.decode('ascii', 'surrogateescape') - - re_numeric_part = re.compile(r"^(\d+)") def numeric_part(s): @@ -183,21 +171,8 @@ class object, used to create cursors (keyword only) self.encoding = 'ascii' # overridden in set_character_set() db = proxy(self) - # Note: string_literal() is called for bytes object on Python 3 (via bytes_literal) - def string_literal(obj, dummy=None): - return db.string_literal(obj) - - if PY2: - # unicode_literal is called for only unicode object. - def unicode_literal(u, dummy=None): - return db.string_literal(u.encode(db.encoding)) - else: - # unicode_literal() is called for arbitrary object. - def unicode_literal(u, dummy=None): - return db.string_literal(str(u).encode(db.encoding)) - - def bytes_literal(obj, dummy=None): - return b'_binary' + db.string_literal(obj) + def unicode_literal(u, dummy=None): + return db.string_literal(u.encode(db.encoding)) def string_decoder(s): return s.decode(db.encoding) @@ -214,7 +189,6 @@ def string_decoder(s): FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB): self.converter[t].append((None, string_decoder)) - self.encoders[bytes] = string_literal self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: @@ -250,7 +224,7 @@ def _bytes_literal(self, bs): return x def _tuple_literal(self, t): - return "(%s)" % (','.join(map(self.literal, t))) + return b"(%s)" % (b','.join(map(self.literal, t))) def literal(self, o): """If o is a single object, returns an SQL literal as a string. @@ -260,7 +234,9 @@ def literal(self, o): Non-standard. For internal use; do not use this in your applications. """ - if isinstance(o, bytearray): + if isinstance(o, unicode): + s = self.string_literal(o.encode(self.encoding)) + elif isinstance(o, bytearray): s = self._bytes_literal(o) elif not PY2 and isinstance(o, bytes): s = self._bytes_literal(o) @@ -268,13 +244,9 @@ def literal(self, o): s = self._tuple_literal(o) else: s = self.escape(o, self.encoders) - # Python 3(~3.4) doesn't support % operation for bytes object. - # We should decode it before using %. - # Decoding with ascii and surrogateescape allows convert arbitrary - # bytes to unicode and back again. - # See http://python.org/dev/peps/pep-0383/ - if not PY2 and isinstance(s, (bytes, bytearray)): - return _fast_surrogateescape(s) + if isinstance(s, unicode): + s = s.encode(self.encoding) + assert isinstance(s, bytes) return s def begin(self): @@ -282,7 +254,7 @@ def begin(self): This method is not used when autocommit=False (default). """ - self.query("BEGIN") + self.query(b"BEGIN") if not hasattr(_mysql.connection, 'warning_count'): diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index c13e4265..20d919f7 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -53,7 +53,7 @@ def Str2Set(s): def Set2Str(s, d): # Only support ascii string. Not tested. - return string_literal(','.join(s), d) + return string_literal(','.join(s)) def Thing2Str(s, d): """Convert something into a string via str().""" @@ -80,7 +80,7 @@ def Thing2Literal(o, d): MySQL-3.23 or newer, string_literal() is a method of the _mysql.MYSQL object, and this function will be overridden with that method when the connection is created.""" - return string_literal(o, d) + return string_literal(o) def Decimal2Literal(o, d): return format(o, 'f') diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 97908249..9a5e76f9 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -15,13 +15,6 @@ NotSupportedError, ProgrammingError) -PY2 = sys.version_info[0] == 2 -if PY2: - text_type = unicode -else: - text_type = str - - #: Regular expression for :meth:`Cursor.executemany`. #: executemany only supports simple bulk insert. #: You can use it to load large dataset. @@ -95,31 +88,28 @@ def __exit__(self, *exc_info): del exc_info self.close() - def _ensure_bytes(self, x, encoding=None): - if isinstance(x, text_type): - x = x.encode(encoding) - elif isinstance(x, (tuple, list)): - x = type(x)(self._ensure_bytes(v, encoding=encoding) for v in x) - return x - def _escape_args(self, args, conn): - ensure_bytes = partial(self._ensure_bytes, encoding=conn.encoding) + encoding = conn.encoding + literal = conn.literal + + def ensure_bytes(x): + if isinstance(x, unicode): + return x.encode(encoding) + elif isinstance(x, tuple): + return tuple(map(ensure_bytes, x)) + elif isinstance(x, list): + return list(map(ensure_bytes, x)) + return x if isinstance(args, (tuple, list)): - if PY2: - args = tuple(map(ensure_bytes, args)) - return tuple(conn.literal(arg) for arg in args) + return tuple(literal(ensure_bytes(arg)) for arg in args) elif isinstance(args, dict): - if PY2: - args = dict((ensure_bytes(key), ensure_bytes(val)) for - (key, val) in args.items()) - return dict((key, conn.literal(val)) for (key, val) in args.items()) + return {ensure_bytes(key): literal(ensure_bytes(val)) + for (key, val) in args.items()} else: # If it's not a dictionary let's try escaping it anyways. # Worst case it will throw a Value error - if PY2: - args = ensure_bytes(args) - return conn.literal(args) + return literal(ensure_bytes(args)) def _check_executed(self): if not self._executed: @@ -186,14 +176,7 @@ def execute(self, query, args=None): pass db = self._get_db() - # NOTE: - # Python 2: query should be bytes when executing %. - # All unicode in args should be encoded to bytes on Python 2. - # Python 3: query should be str (unicode) when executing %. - # All bytes in args should be decoded with ascii and surrogateescape on Python 3. - # db.literal(obj) always returns str. - - if PY2 and isinstance(query, unicode): + if isinstance(query, unicode): query = query.encode(db.encoding) if args is not None: @@ -201,16 +184,12 @@ def execute(self, query, args=None): args = dict((key, db.literal(item)) for key, item in args.items()) else: args = tuple(map(db.literal, args)) - if not PY2 and isinstance(query, (bytes, bytearray)): - query = query.decode(db.encoding) try: query = query % args except TypeError as m: raise ProgrammingError(str(m)) - if isinstance(query, unicode): - query = query.encode(db.encoding, 'surrogateescape') - + assert isinstance(query, (bytes, bytearray)) res = self._query(query) return res @@ -247,29 +226,19 @@ def executemany(self, query, args): def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding): conn = self._get_db() escape = self._escape_args - if isinstance(prefix, text_type): + if isinstance(prefix, unicode): prefix = prefix.encode(encoding) - if PY2 and isinstance(values, text_type): + if isinstance(values, unicode): values = values.encode(encoding) - if isinstance(postfix, text_type): + if isinstance(postfix, unicode): postfix = postfix.encode(encoding) sql = bytearray(prefix) args = iter(args) v = values % escape(next(args), conn) - if isinstance(v, text_type): - if PY2: - v = v.encode(encoding) - else: - v = v.encode(encoding, 'surrogateescape') sql += v rows = 0 for arg in args: v = values % escape(arg, conn) - if isinstance(v, text_type): - if PY2: - v = v.encode(encoding) - else: - v = v.encode(encoding, 'surrogateescape') if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length: rows += self.execute(sql + postfix) sql = bytearray(prefix) @@ -308,22 +277,19 @@ def callproc(self, procname, args=()): to advance through all result sets; otherwise you may get disconnected. """ - db = self._get_db() + if isinstance(procname, unicode): + procname = procname.encode(db.encoding) if args: - fmt = '@_{0}_%d=%s'.format(procname) - q = 'SET %s' % ','.join(fmt % (index, db.literal(arg)) - for index, arg in enumerate(args)) - if isinstance(q, unicode): - q = q.encode(db.encoding, 'surrogateescape') + fmt = b'@_' + procname + b'_%d=%s' + q = b'SET %s' % b','.join(fmt % (index, db.literal(arg)) + for index, arg in enumerate(args)) self._query(q) self.nextset() - q = "CALL %s(%s)" % (procname, - ','.join(['@_%s_%d' % (procname, i) - for i in range(len(args))])) - if isinstance(q, unicode): - q = q.encode(db.encoding, 'surrogateescape') + q = b"CALL %s(%s)" % (procname, + b','.join([b'@_%s_%d' % (procname, i) + for i in range(len(args))])) self._query(q) return args diff --git a/MySQLdb/times.py b/MySQLdb/times.py index 510d1c7c..a7eaa53b 100644 --- a/MySQLdb/times.py +++ b/MySQLdb/times.py @@ -124,11 +124,11 @@ def Date_or_None(s): def DateTime2literal(d, c): """Format a DateTime object as an ISO timestamp.""" - return string_literal(format_TIMESTAMP(d), c) + return string_literal(format_TIMESTAMP(d)) def DateTimeDelta2literal(d, c): """Format a DateTimeDelta object as a time.""" - return string_literal(format_TIMEDELTA(d),c) + return string_literal(format_TIMEDELTA(d)) def mysql_timestamp_converter(s): """Convert a MySQL TIMESTAMP to a Timestamp object.""" From 819688b630be850ee485d265b60cd9fa4040c07e Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 7 Dec 2018 13:50:05 +0900 Subject: [PATCH 147/396] Simplify converters (#304) --- MySQLdb/_mysql.c | 3 --- MySQLdb/converters.py | 33 ++++++++----------------------- tests/test_MySQLdb_nonstandard.py | 4 ---- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 6c12b923..a90d7ab1 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -2730,9 +2730,6 @@ init_mysql(void) _mysql_NewException(dict, edict, "NotSupportedError"))) goto error; Py_DECREF(emod); - if (!(_mysql_NULL = PyString_FromString("NULL"))) - goto error; - if (PyDict_SetItemString(dict, "NULL", _mysql_NULL)) goto error; error: if (PyErr_Occurred()) { PyErr_SetString(PyExc_ImportError, "_mysql: init failed"); diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 20d919f7..6394a5c6 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -30,8 +30,9 @@ (with the copy() method), modify the copies, and then pass them to MySQL.connect(). """ +from decimal import Decimal -from MySQLdb._mysql import string_literal, escape, NULL +from MySQLdb._mysql import string_literal, escape from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * from MySQLdb.compat import PY2, long @@ -46,10 +47,8 @@ ArrayType = array.array -def Bool2Str(s, d): return str(int(s)) - -def Str2Set(s): - return set([ i for i in s.split(',') if i ]) +def Bool2Str(s, d): + return b'1' if s else b'0' def Set2Str(s, d): # Only support ascii string. Not tested. @@ -73,7 +72,7 @@ def Float2Str(o, d): def None2NULL(o, d): """Convert None to NULL.""" - return NULL # duh + return b"NULL" def Thing2Literal(o, d): """Convert something into a SQL string literal. If using @@ -85,9 +84,6 @@ def Thing2Literal(o, d): def Decimal2Literal(o, d): return format(o, 'f') -def char_array(s): - return array.array('c', s) - def array2Str(o, d): return Thing2Literal(o.tostring(), d) @@ -109,18 +105,18 @@ def quote_tuple(t, d): DateTimeDeltaType: DateTimeDelta2literal, str: Thing2Literal, # default set: Set2Str, + Decimal: Decimal2Literal, FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, FIELD_TYPE.LONG: long, FIELD_TYPE.FLOAT: float, FIELD_TYPE.DOUBLE: float, - FIELD_TYPE.DECIMAL: float, - FIELD_TYPE.NEWDECIMAL: float, + FIELD_TYPE.DECIMAL: Decimal, + FIELD_TYPE.NEWDECIMAL: Decimal, FIELD_TYPE.LONGLONG: long, FIELD_TYPE.INT24: int, FIELD_TYPE.YEAR: int, - FIELD_TYPE.SET: Str2Set, FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter, FIELD_TYPE.DATETIME: DateTime_or_None, FIELD_TYPE.TIME: TimeDelta_or_None, @@ -134,16 +130,3 @@ def quote_tuple(t, d): FIELD_TYPE.VAR_STRING: _bytes_or_str, FIELD_TYPE.VARCHAR: _bytes_or_str, } - -if PY2: - conversions[unicode] = Unicode2Str -else: - conversions[bytes] = Thing2Literal - -try: - from decimal import Decimal - conversions[FIELD_TYPE.DECIMAL] = Decimal - conversions[FIELD_TYPE.NEWDECIMAL] = Decimal - conversions[Decimal] = Decimal2Literal -except ImportError: - pass diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index e60a2a77..55cfc630 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -25,10 +25,6 @@ def test_set_inequality_membership(self): class TestCoreModule(unittest.TestCase): """Core _mysql module features.""" - def test_NULL(self): - """Should have a NULL constant.""" - self.assertEqual(_mysql.NULL, 'NULL') - def test_version(self): """Version information sanity.""" self.assertTrue(isinstance(_mysql.__version__, str)) From e48e393e68112f88f3db575ef4a34b1c5ad08370 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 7 Dec 2018 13:51:17 +0900 Subject: [PATCH 148/396] Update compat --- MySQLdb/compat.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MySQLdb/compat.py b/MySQLdb/compat.py index 70580b61..8fe6709a 100644 --- a/MySQLdb/compat.py +++ b/MySQLdb/compat.py @@ -1,12 +1,12 @@ import sys -if sys.version_info[0] == 3: - PY2 = False - unicode = str - unichr = chr - long = int -else: +if sys.version_info[0] == 2: PY2 = True unicode = unicode unichr = unichr long = long +else: + PY2 = False + unicode = str + unichr = chr + long = int From ae6b108b56fa5f66f5dcd95cd20ffd1a80fa96c1 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 10 Dec 2018 19:08:19 +0900 Subject: [PATCH 149/396] Remove HAVE_MYSQL_OPT_TIMEOUTS Drop MySQL<5.1.12 support --- MySQLdb/_mysql.c | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index a90d7ab1..2d236982 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -92,12 +92,6 @@ typedef struct { extern PyTypeObject _mysql_ResultObject_Type; -/* According to https://dev.mysql.com/doc/refman/5.1/en/mysql-options.html - The MYSQL_OPT_READ_TIMEOUT appear in the version 5.1.12 */ -#if MYSQL_VERSION_ID > 50112 -#define HAVE_MYSQL_OPT_TIMEOUTS 1 -#endif - PyObject * _mysql_Exception(_mysql_ConnectionObject *c) { @@ -382,16 +376,11 @@ _mysql_ConnectionObject_Initialize( "read_default_file", "read_default_group", "client_flag", "ssl", "local_infile", -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - "read_timeout", - "write_timeout", -#endif + "read_timeout", "write_timeout", NULL } ; int connect_timeout = 0; -#ifdef HAVE_MYSQL_OPT_TIMEOUTS int read_timeout = 0; int write_timeout = 0; -#endif int compress = -1, named_pipe = -1, local_infile = -1; char *init_command=NULL, *read_default_file=NULL, @@ -401,11 +390,7 @@ _mysql_ConnectionObject_Initialize( self->open = 0; if (!PyArg_ParseTupleAndKeywords(args, kwargs, -#ifdef HAVE_MYSQL_OPT_TIMEOUTS "|ssssisOiiisssiOiii:connect", -#else - "|ssssisOiiisssiOi:connect", -#endif kwlist, &host, &user, &passwd, &db, &port, &unix_socket, &conv, @@ -414,11 +399,9 @@ _mysql_ConnectionObject_Initialize( &init_command, &read_default_file, &read_default_group, &client_flag, &ssl, - &local_infile -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - , &read_timeout - , &write_timeout -#endif + &local_infile, + &read_timeout, + &write_timeout )) return -1; @@ -448,7 +431,6 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout); } -#ifdef HAVE_MYSQL_OPT_TIMEOUTS if (read_timeout) { unsigned int timeout = read_timeout; mysql_options(&(self->connection), MYSQL_OPT_READ_TIMEOUT, @@ -459,7 +441,6 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_WRITE_TIMEOUT, (char *)&timeout); } -#endif if (compress != -1) { mysql_options(&(self->connection), MYSQL_OPT_COMPRESS, 0); client_flag |= CLIENT_COMPRESS; From c7188e64bbe17f31739210ae8219582efb175913 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 10 Dec 2018 20:26:37 +0900 Subject: [PATCH 150/396] Update HISTORY --- HISTORY.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 1a5fd824..fd47e415 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -22,6 +22,12 @@ Release: TBD * Remove automatic MySQL warning checking. +* Drop support for MySQL Connector/C with MySQL<5.1.12. + +* Remove ``_mysql.NULL`` constant. + +* Support non-ASCII field name with non-UTF-8 connection encoding. (#210) + ====================== What's new in 1.3.14 From 3bea8cd8dd89c1d078a6ff2a36ef5804429f398f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 11 Dec 2018 11:50:42 +0900 Subject: [PATCH 151/396] Faster string and int decoder (#305) --- MySQLdb/_mysql.c | 79 +++++++++++++++++++++++++++++++----------- MySQLdb/connections.py | 7 ++-- MySQLdb/converters.py | 22 ++++++------ 3 files changed, 71 insertions(+), 37 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 2d236982..53e57f85 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -37,6 +37,8 @@ PERFORMANCE OF THIS SOFTWARE. #include "Python.h" #if PY_MAJOR_VERSION >= 3 #define IS_PY3K +#define PyInt_Type PyLong_Type +#define PyInt_FromString PyLong_FromString #define PyInt_FromLong(n) PyLong_FromLong(n) #define PyInt_Check(n) PyLong_Check(n) #define PyInt_AS_LONG(n) PyLong_AS_LONG(n) @@ -87,6 +89,7 @@ typedef struct { int use; char has_next; PyObject *converter; + const char *encoding; } _mysql_ResultObject; extern PyTypeObject _mysql_ResultObject_Type; @@ -203,6 +206,25 @@ _mysql_Exception(_mysql_ConnectionObject *c) return NULL; } +static const char *utf8 = "utf8"; + +static const char* +_get_encoding(MYSQL *mysql) +{ + MY_CHARSET_INFO cs; + mysql_get_character_set_info(mysql, &cs); + if (strncmp(utf8, cs.csname, 4) == 0) { // utf8, utf8mb3, utf8mb4 + return utf8; + } + else if (strncmp("koi8r", cs.csname, 5) == 0) { + return "koi8_r"; + } + else if (strncmp("koi8u", cs.csname, 5) == 0) { + return "koi8_u"; + } + return cs.csname; +} + static char _mysql_thread_safe__doc__[] = "Indicates whether the client is compiled as thread-safe."; @@ -251,6 +273,9 @@ _mysql_ResultObject_Initialize( self->result = result; self->has_next = (char)mysql_more_results(&(conn->connection)); Py_END_ALLOW_THREADS ; + + self->encoding = _get_encoding(&(conn->connection)); + //fprintf(stderr, "encoding=%s\n", self->encoding); if (!result) { if (mysql_errno(&(conn->connection))) { _mysql_Exception(conn); @@ -289,6 +314,8 @@ _mysql_ResultObject_Initialize( int j, n2=PySequence_Size(fun); if (fields[i].charsetnr != 63) { /* maaagic */ flags &= ~BINARY_FLAG; + } else { + flags |= BINARY_FLAG; } for (j=0; jconnection, &cs); - if (strncmp("utf8", cs.name, 4) == 0) { // utf8, utf8mb3, utf8mb4 - isutf8 = 1; - } - n = mysql_num_fields(self->result); fields = mysql_fetch_fields(self->result); if (!(d = PyTuple_New(n))) return NULL; @@ -1029,10 +1047,10 @@ _mysql_ResultObject_describe( PyObject *t; #ifdef IS_PY3K PyObject *name; - if (isutf8) { + if (self->encoding == utf8) { name = PyUnicode_DecodeUTF8(fields[i].name, fields[i].name_length, "replace"); } else { - name = PyUnicode_Decode(fields[i].name, fields[i].name_length, cs.name, "replace"); + name = PyUnicode_Decode(fields[i].name, fields[i].name_length, self->encoding, "replace"); } if (name == NULL) { goto error; @@ -1089,9 +1107,10 @@ _mysql_ResultObject_field_flags( static PyObject * _mysql_field_to_python( PyObject *converter, - char *rowitem, + const char *rowitem, unsigned long length, - MYSQL_FIELD *field) + MYSQL_FIELD *field, + const char *encoding) { PyObject *v; #ifdef IS_PY3K @@ -1110,7 +1129,27 @@ _mysql_field_to_python( } #endif if (rowitem) { - if (converter != Py_None) { + if (converter == (PyObject*)&PyUnicode_Type) { + if (encoding == utf8) { + //fprintf(stderr, "decoding with utf8!\n"); + v = PyUnicode_DecodeUTF8(rowitem, length, NULL); + } else { + //fprintf(stderr, "decoding with %s\n", encoding); + v = PyUnicode_Decode(rowitem, length, encoding, NULL); + } + } + else if (converter == (PyObject*)&PyBytes_Type) { + //fprintf(stderr, "decoding with bytes\n", encoding); + v = PyBytes_FromStringAndSize(rowitem, length); + } + else if (converter == (PyObject*)&PyInt_Type) { + //fprintf(stderr, "decoding with int\n", encoding); + v = PyInt_FromString(rowitem, NULL, 10); + } + else if (converter != Py_None) { + //fprintf(stderr, "decoding with callback\n"); + //PyObject_Print(converter, stderr, 0); + //fprintf(stderr, "\n"); v = PyObject_CallFunction(converter, #ifdef IS_PY3K binary ? "y#" : "s#", @@ -1120,6 +1159,7 @@ _mysql_field_to_python( rowitem, (int)length); } else { + //fprintf(stderr, "converter=None\n"); #ifdef IS_PY3K if (!binary) { v = PyUnicode_FromStringAndSize(rowitem, (int)length); @@ -1153,7 +1193,7 @@ _mysql_row_to_tuple( for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); if (!v) goto error; PyTuple_SET_ITEM(r, i, v); } @@ -1180,7 +1220,7 @@ _mysql_row_to_dict( for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); if (!v) goto error; if (!PyMapping_HasKeyString(r, fields[i].name)) { PyMapping_SetItemString(r, fields[i].name, v); @@ -1219,7 +1259,7 @@ _mysql_row_to_dict_old( for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); if (!v) goto error; { int len=0; @@ -1422,8 +1462,7 @@ _mysql_ConnectionObject_set_character_set( err = mysql_set_character_set(&(self->connection), s); Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } #if MYSQL_VERSION_ID >= 50010 @@ -2634,12 +2673,10 @@ init_mysql(void) _mysql_ConnectionObject_Type.ob_type = &PyType_Type; _mysql_ResultObject_Type.ob_type = &PyType_Type; #endif -#if PY_VERSION_HEX >= 0x02020000 _mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc; _mysql_ConnectionObject_Type.tp_new = PyType_GenericNew; _mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc; _mysql_ResultObject_Type.tp_new = PyType_GenericNew; -#endif #ifdef IS_PY3K if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) return NULL; diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 27a8d437..e902912f 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -114,7 +114,7 @@ class object, used to create cursors (keyword only) documentation for the MySQL C API for some hints on what they do. """ from MySQLdb.constants import CLIENT, FIELD_TYPE - from MySQLdb.converters import conversions + from MySQLdb.converters import conversions, _bytes_or_str from weakref import proxy kwargs2 = kwargs.copy() @@ -174,9 +174,6 @@ class object, used to create cursors (keyword only) def unicode_literal(u, dummy=None): return db.string_literal(u.encode(db.encoding)) - def string_decoder(s): - return s.decode(db.encoding) - if not charset: charset = self.character_set_name() self.set_character_set(charset) @@ -187,7 +184,7 @@ def string_decoder(s): if use_unicode: for t in (FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING, FIELD_TYPE.VARCHAR, FIELD_TYPE.TINY_BLOB, FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB): - self.converter[t].append((None, string_decoder)) + self.converter[t] = _bytes_or_str self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 6394a5c6..7b1eb035 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -35,7 +35,7 @@ from MySQLdb._mysql import string_literal, escape from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * -from MySQLdb.compat import PY2, long +from MySQLdb.compat import PY2, long, unicode NoneType = type(None) @@ -91,7 +91,7 @@ def quote_tuple(t, d): return "(%s)" % (','.join(escape_sequence(t, d))) # bytes or str regarding to BINARY_FLAG. -_bytes_or_str = [(FLAG.BINARY, bytes)] +_bytes_or_str = ((FLAG.BINARY, bytes), (None, unicode)) conversions = { int: Thing2Str, @@ -109,12 +109,12 @@ def quote_tuple(t, d): FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, - FIELD_TYPE.LONG: long, + FIELD_TYPE.LONG: int, FIELD_TYPE.FLOAT: float, FIELD_TYPE.DOUBLE: float, FIELD_TYPE.DECIMAL: Decimal, FIELD_TYPE.NEWDECIMAL: Decimal, - FIELD_TYPE.LONGLONG: long, + FIELD_TYPE.LONGLONG: int, FIELD_TYPE.INT24: int, FIELD_TYPE.YEAR: int, FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter, @@ -122,11 +122,11 @@ def quote_tuple(t, d): FIELD_TYPE.TIME: TimeDelta_or_None, FIELD_TYPE.DATE: Date_or_None, - FIELD_TYPE.TINY_BLOB: _bytes_or_str, - FIELD_TYPE.MEDIUM_BLOB: _bytes_or_str, - FIELD_TYPE.LONG_BLOB: _bytes_or_str, - FIELD_TYPE.BLOB: _bytes_or_str, - FIELD_TYPE.STRING: _bytes_or_str, - FIELD_TYPE.VAR_STRING: _bytes_or_str, - FIELD_TYPE.VARCHAR: _bytes_or_str, + FIELD_TYPE.TINY_BLOB: bytes, + FIELD_TYPE.MEDIUM_BLOB: bytes, + FIELD_TYPE.LONG_BLOB: bytes, + FIELD_TYPE.BLOB: bytes, + FIELD_TYPE.STRING: bytes, + FIELD_TYPE.VAR_STRING: bytes, + FIELD_TYPE.VARCHAR: bytes, } From 2760691af000c823f035ba2ad32dc520a1a52cf0 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 11 Dec 2018 14:57:15 +0900 Subject: [PATCH 152/396] 1.4.0rc1 --- HISTORY.rst | 1 + metadata.cfg | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index fd47e415..098a3047 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -28,6 +28,7 @@ Release: TBD * Support non-ASCII field name with non-UTF-8 connection encoding. (#210) +* Optimize decoding speed of string and integer types. ====================== What's new in 1.3.14 diff --git a/metadata.cfg b/metadata.cfg index 6901e7af..4ad40d37 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,11 +1,9 @@ [metadata] -version: 1.3.14 -version_info: (1,3,14,'final',0) +version: 1.4.0 +version_info: (1,4,0,'rc',1) description: Python interface to MySQL -author: Andy Dustman -author_email: farcepest@gmail.com -maintainer: INADA Naoki -maintainer_email: songofacandy@gmail.com +author: Inada Naoki +author_email: songofacandy@gmail.com license: GPL platforms: ALL url: https://github.com/PyMySQL/mysqlclient-python From b14b2bd6843fa243078c62b4af210aee0e8e2b9b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 11 Dec 2018 16:16:29 +0900 Subject: [PATCH 153/396] fix version string --- metadata.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata.cfg b/metadata.cfg index 4ad40d37..82fdeb0f 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,5 +1,5 @@ [metadata] -version: 1.4.0 +version: 1.4.0rc1 version_info: (1,4,0,'rc',1) description: Python interface to MySQL author: Inada Naoki From 9f31f592f226c1687296e8dd0730b21f9c9285a3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 11 Dec 2018 16:19:47 +0900 Subject: [PATCH 154/396] Removed MySQLdb.constants.REFRESH --- MySQLdb/constants/REFRESH.py | 17 ----------------- MySQLdb/constants/__init__.py | 2 +- doc/MySQLdb.constants.rst | 8 -------- metadata.cfg | 1 - 4 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 MySQLdb/constants/REFRESH.py diff --git a/MySQLdb/constants/REFRESH.py b/MySQLdb/constants/REFRESH.py deleted file mode 100644 index 4a08b94e..00000000 --- a/MySQLdb/constants/REFRESH.py +++ /dev/null @@ -1,17 +0,0 @@ -"""MySQL REFRESH Constants - -These constants seem to mostly deal with things internal to the -MySQL server. Forget you saw this. - -""" - -GRANT = 1 -LOG = 2 -TABLES = 4 -HOSTS = 8 -STATUS = 16 -THREADS = 32 -SLAVE = 64 -MASTER = 128 -READ_LOCK = 16384 -FAST = 32768 diff --git a/MySQLdb/constants/__init__.py b/MySQLdb/constants/__init__.py index 3da4a0e7..3e774cd9 100644 --- a/MySQLdb/constants/__init__.py +++ b/MySQLdb/constants/__init__.py @@ -1 +1 @@ -__all__ = ['CR', 'FIELD_TYPE','CLIENT','REFRESH','ER','FLAG'] +__all__ = ['CR', 'FIELD_TYPE','CLIENT','ER','FLAG'] diff --git a/doc/MySQLdb.constants.rst b/doc/MySQLdb.constants.rst index 9276f8fa..5c9a5389 100644 --- a/doc/MySQLdb.constants.rst +++ b/doc/MySQLdb.constants.rst @@ -49,11 +49,3 @@ constants Package :undoc-members: :show-inheritance: -:mod:`REFRESH` Module ---------------------- - -.. automodule:: MySQLdb.constants.REFRESH - :members: - :undoc-members: - :show-inheritance: - diff --git a/metadata.cfg b/metadata.cfg index 82fdeb0f..589e52f7 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -40,4 +40,3 @@ py_modules: MySQLdb.constants.ER MySQLdb.constants.FIELD_TYPE MySQLdb.constants.FLAG - MySQLdb.constants.REFRESH From 98dedd93e77066231e321738e49cf66a7ad04e7a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 11 Dec 2018 16:20:43 +0900 Subject: [PATCH 155/396] Update HISTORY --- HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 098a3047..54b09a53 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -30,6 +30,8 @@ Release: TBD * Optimize decoding speed of string and integer types. +* Removed ``MySQLdb.constants.REFRESH`` module. + ====================== What's new in 1.3.14 ====================== From 6cc21d9ead9bb2002db9c1844aa5f7583c5e0501 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 12 Dec 2018 16:15:24 +0900 Subject: [PATCH 156/396] Update INSTALL.rst --- INSTALL.rst | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 2588b99e..f5be3f4e 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -8,17 +8,17 @@ MySQLdb Installation Prerequisites ------------- -+ Python 2.7, 3.4 or higher ++ Python 2.7, 3.5 or higher + setuptools - * http://pypi.python.org/pypi/setuptools + * https://pypi.org/project/setuptools/ + MySQL 5.5 or higher - * http://www.mysql.com/downloads/ + * https://www.mysql.com/downloads/ - * MySQL-5.0 may work, but not supported. + * MySQL 5.1 may work, but not supported. + C compiler @@ -44,8 +44,7 @@ edit the [options] section of site.cfg: static if True, try to link against a static library; otherwise link - against dynamic libraries (default). You may need static linking - to use the embedded server. + against dynamic libraries (default). This option doesn't work for MySQL>5.6 since libmysqlclient requires libstdc++. If you want to use, add `-lstdc++` to mysql_config manually. @@ -94,25 +93,11 @@ contributes one, I will make it available. Several OS vendors have their own packages available. -RPMs -.... - -If you prefer to install RPMs, you can use the bdist_rpm command with -setup.py. This only builds the RPM; it does not install it. You may -want to use the --python=XXX option, where XXX is the name of the -Python executable, i.e. python, python2, python2.4; the default is -python. Using this will incorporate the Python executable name into -the package name for the RPM so you have install the package multiple -times if you need to support more than one version of Python. You can -also set this in setup.cfg. - - Red Hat Linux ............. MySQL-python is pre-packaged in Red Hat Linux 7.x and newer. This -includes Fedora Core and Red Hat Enterprise Linux. You can also -build your own RPM packages as described above. +includes Fedora Core and Red Hat Enterprise Linux. Debian GNU/Linux From 80278a266e1ccc7d28c768dd430eb5e800395349 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 12 Dec 2018 16:52:03 +0900 Subject: [PATCH 157/396] Update ER and CR (#307) --- MySQLdb/constants/CR.py | 9 +- MySQLdb/constants/ER.py | 251 ++-------------------------------------- 2 files changed, 12 insertions(+), 248 deletions(-) diff --git a/MySQLdb/constants/CR.py b/MySQLdb/constants/CR.py index 1b047243..753408ee 100644 --- a/MySQLdb/constants/CR.py +++ b/MySQLdb/constants/CR.py @@ -83,7 +83,6 @@ SHARED_MEMORY_CONNECT_SET_ERROR = 2046 CONN_UNKNOW_PROTOCOL = 2047 INVALID_CONN_HANDLE = 2048 -SECURE_AUTH = 2049 UNUSED_1 = 2049 FETCH_CANCELED = 2050 NO_DATA = 2051 @@ -94,11 +93,11 @@ STMT_CLOSED = 2056 NEW_STMT_METADATA = 2057 ALREADY_CONNECTED = 2058 -AUTH_PLUGIN_CANNOT_LOAD = 2058 -ALREADY_CONNECTED = 2059 AUTH_PLUGIN_CANNOT_LOAD = 2059 DUPLICATE_CONNECTION_ATTR = 2060 -PLUGIN_FUNCTION_NOT_SUPPORTED = 2060 AUTH_PLUGIN_ERR = 2061 +INSECURE_API_ERR = 2062 +FILE_NAME_TOO_LONG = 2063 +SSL_FIPS_MODE_ERR = 2064 MAX_ERROR = 2999 -ERROR_LAST = 2061 +ERROR_LAST = 2064 diff --git a/MySQLdb/constants/ER.py b/MySQLdb/constants/ER.py index 59db2e1f..2e623b51 100644 --- a/MySQLdb/constants/ER.py +++ b/MySQLdb/constants/ER.py @@ -2,7 +2,6 @@ These constants are error codes for the bulk of the error conditions that may occur. - """ if __name__ == "__main__": @@ -35,8 +34,6 @@ ERROR_FIRST = 1000 -HASHCHK = 1000 -NISAMCHK = 1001 NO = 1002 YES = 1003 CANT_CREATE_FILE = 1004 @@ -44,27 +41,20 @@ CANT_CREATE_DB = 1006 DB_CREATE_EXISTS = 1007 DB_DROP_EXISTS = 1008 -DB_DROP_DELETE = 1009 DB_DROP_RMDIR = 1010 -CANT_DELETE_FILE = 1011 CANT_FIND_SYSTEM_REC = 1012 CANT_GET_STAT = 1013 -CANT_GET_WD = 1014 CANT_LOCK = 1015 CANT_OPEN_FILE = 1016 FILE_NOT_FOUND = 1017 CANT_READ_DIR = 1018 -CANT_SET_WD = 1019 CHECKREAD = 1020 -DISK_FULL = 1021 DUP_KEY = 1022 -ERROR_ON_CLOSE = 1023 ERROR_ON_READ = 1024 ERROR_ON_RENAME = 1025 ERROR_ON_WRITE = 1026 FILE_USED = 1027 FILSORT_ABORT = 1028 -FORM_NOT_FOUND = 1029 GET_ERRNO = 1030 ILLEGAL_HA = 1031 KEY_NOT_FOUND = 1032 @@ -74,7 +64,6 @@ OPEN_AS_READONLY = 1036 OUTOFMEMORY = 1037 OUT_OF_SORTMEMORY = 1038 -UNEXPECTED_EOF = 1039 CON_COUNT_ERROR = 1040 OUT_OF_RESOURCES = 1041 BAD_HOST_ERROR = 1042 @@ -112,8 +101,6 @@ TOO_BIG_FIELDLENGTH = 1074 WRONG_AUTO_KEY = 1075 READY = 1076 -NORMAL_SHUTDOWN = 1077 -GOT_SIGNAL = 1078 SHUTDOWN_COMPLETE = 1079 FORCING_CLOSE = 1080 IPSOCK_ERROR = 1081 @@ -128,7 +115,6 @@ CANT_REMOVE_ALL_FIELDS = 1090 CANT_DROP_FIELD_OR_KEY = 1091 INSERT_INFO = 1092 -INSERT_TABLE_USED = 1093 UPDATE_TABLE_USED = 1093 NO_SUCH_THREAD = 1094 KILL_DENIED_ERROR = 1095 @@ -156,7 +142,7 @@ TOO_MANY_FIELDS = 1117 TOO_BIG_ROWSIZE = 1118 STACK_OVERRUN = 1119 -WRONG_OUTER_JOIN = 1120 +WRONG_OUTER_JOIN_UNUSED = 1120 NULL_COLUMN_IN_INDEX = 1121 CANT_FIND_UDF = 1122 CANT_INITIALIZE_UDF = 1123 @@ -186,10 +172,6 @@ NONEXISTING_TABLE_GRANT = 1147 NOT_ALLOWED_COMMAND = 1148 SYNTAX_ERROR = 1149 -DELAYED_CANT_CHANGE_LOCK = 1150 -UNUSED1 = 1150 -TOO_MANY_DELAYED_THREADS = 1151 -UNUSED2 = 1151 ABORTING_CONNECTION = 1152 NET_PACKET_TOO_LARGE = 1153 NET_READ_ERROR_FROM_PIPE = 1154 @@ -203,8 +185,6 @@ TOO_LONG_STRING = 1162 TABLE_CANT_HANDLE_BLOB = 1163 TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 -DELAYED_INSERT_TABLE_LOCKED = 1165 -UNUSED3 = 1165 WRONG_COLUMN_NAME = 1166 WRONG_KEY_COLUMN = 1167 WRONG_MRG_TABLE = 1168 @@ -213,7 +193,6 @@ PRIMARY_CANT_HAVE_NULL = 1171 TOO_MANY_ROWS = 1172 REQUIRES_PRIMARY_KEY = 1173 -NO_RAID_COMPILED = 1174 UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 KEY_DOES_NOT_EXITS = 1176 CHECK_NO_SUCH_TABLE = 1177 @@ -222,11 +201,7 @@ ERROR_DURING_COMMIT = 1180 ERROR_DURING_ROLLBACK = 1181 ERROR_DURING_FLUSH_LOGS = 1182 -ERROR_DURING_CHECKPOINT = 1183 NEW_ABORTING_CONNECTION = 1184 -DUMP_NOT_IMPLEMENTED = 1185 -FLUSH_MASTER_BINLOG_CLOSED = 1186 -INDEX_REBUILD = 1187 MASTER = 1188 MASTER_NET_READ = 1189 MASTER_NET_WRITE = 1190 @@ -237,7 +212,6 @@ CRASHED_ON_REPAIR = 1195 WARNING_NOT_COMPLETE_ROLLBACK = 1196 TRANS_CACHE_FULL = 1197 -SLAVE_MUST_STOP = 1198 SLAVE_NOT_RUNNING = 1199 BAD_SLAVE = 1200 MASTER_INFO = 1201 @@ -247,19 +221,14 @@ LOCK_WAIT_TIMEOUT = 1205 LOCK_TABLE_FULL = 1206 READ_ONLY_TRANSACTION = 1207 -DROP_DB_WITH_READ_LOCK = 1208 -CREATE_DB_WITH_READ_LOCK = 1209 WRONG_ARGUMENTS = 1210 NO_PERMISSION_TO_CREATE_USER = 1211 -UNION_TABLES_IN_DIFFERENT_DIR = 1212 LOCK_DEADLOCK = 1213 TABLE_CANT_HANDLE_FT = 1214 -TABLE_CANT_HANDLE_FULLTEXT = 1214 CANNOT_ADD_FOREIGN = 1215 NO_REFERENCED_ROW = 1216 ROW_IS_REFERENCED = 1217 CONNECT_TO_MASTER = 1218 -QUERY_ON_MASTER = 1219 ERROR_WHEN_EXECUTING_COMMAND = 1220 WRONG_USAGE = 1221 WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 @@ -285,7 +254,6 @@ SUBQUERY_NO_1_ROW = 1242 UNKNOWN_STMT_HANDLER = 1243 CORRUPT_HELP_DB = 1244 -CYCLIC_REFERENCE = 1245 AUTO_CONVERT = 1246 ILLEGAL_REFERENCE = 1247 DERIVED_MUST_HAVE_ALIAS = 1248 @@ -294,8 +262,6 @@ NOT_SUPPORTED_AUTH_MODE = 1251 SPATIAL_CANT_HAVE_NULL = 1252 COLLATION_CHARSET_MISMATCH = 1253 -SLAVE_WAS_RUNNING = 1254 -SLAVE_WAS_NOT_RUNNING = 1255 TOO_BIG_FOR_UNCOMPRESS = 1256 ZLIB_Z_MEM_ERROR = 1257 ZLIB_Z_BUF_ERROR = 1258 @@ -308,7 +274,6 @@ WARN_DATA_TRUNCATED = 1265 WARN_USING_OTHER_HANDLER = 1266 CANT_AGGREGATE_2COLLATIONS = 1267 -DROP_USER = 1268 REVOKE_GRANTS = 1269 CANT_AGGREGATE_3COLLATIONS = 1270 CANT_AGGREGATE_NCOLLATIONS = 1271 @@ -322,7 +287,6 @@ UNTIL_COND_IGNORED = 1279 WRONG_NAME_FOR_INDEX = 1280 WRONG_NAME_FOR_CATALOG = 1281 -WARN_QC_RESIZE = 1282 BAD_FT_COLUMN = 1283 UNKNOWN_KEY_CACHE = 1284 WARN_HOSTNAME_WONT_WORK = 1285 @@ -333,7 +297,6 @@ OPTION_PREVENTS_STATEMENT = 1290 DUPLICATED_VALUE_IN_TYPE = 1291 TRUNCATED_WRONG_VALUE = 1292 -TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 INVALID_ON_UPDATE = 1294 UNSUPPORTED_PS = 1295 GET_ERRMSG = 1296 @@ -386,10 +349,8 @@ FPARSER_ERROR_IN_PARAMETER = 1343 FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 VIEW_NO_EXPLAIN = 1345 -FRM_UNKNOWN_TYPE = 1346 WRONG_OBJECT = 1347 NONUPDATEABLE_COLUMN = 1348 -VIEW_SELECT_DERIVED = 1349 VIEW_SELECT_CLAUSE = 1350 VIEW_SELECT_VARIABLE = 1351 VIEW_SELECT_TMPTABLE = 1352 @@ -398,7 +359,6 @@ WARN_VIEW_WITHOUT_KEY = 1355 VIEW_INVALID = 1356 SP_NO_DROP_SP = 1357 -SP_GOTO_IN_HNDLR = 1358 TRG_ALREADY_EXISTS = 1359 TRG_DOES_NOT_EXIST = 1360 TRG_ON_VIEW_OR_TEMP_TABLE = 1361 @@ -412,7 +372,6 @@ VIEW_CHECK_FAILED = 1369 PROCACCESS_DENIED_ERROR = 1370 RELAY_LOG_FAIL = 1371 -PASSWD_LENGTH = 1372 UNKNOWN_TARGET_BINLOG = 1373 IO_ERR_LOG_INDEX_READ = 1374 BINLOG_PURGE_PROHIBITED = 1375 @@ -423,13 +382,6 @@ RELAY_LOG_INIT = 1380 NO_BINARY_LOGGING = 1381 RESERVED_SYNTAX = 1382 -WSAS_FAILED = 1383 -DIFF_GROUPS_PROC = 1384 -NO_GROUP_FOR_PROC = 1385 -ORDER_WITH_PROC = 1386 -LOGGING_PROHIBIT_CHANGING_OF = 1387 -NO_FILE_MAPPING = 1388 -WRONG_MAGIC = 1389 PS_MANY_PARAM = 1390 KEY_PART_0 = 1391 VIEW_CHECKSUM = 1392 @@ -457,10 +409,8 @@ SP_NOT_VAR_ARG = 1414 SP_NO_RETSET = 1415 CANT_CREATE_GEOMETRY_OBJECT = 1416 -FAILED_ROUTINE_BREAK_BINLOG = 1417 BINLOG_UNSAFE_ROUTINE = 1418 BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 -EXEC_STMT_WITH_OPEN_CURSOR = 1420 STMT_HAS_NO_OPEN_CURSOR = 1421 COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 NO_DEFAULT_FOR_VIEW_FIELD = 1423 @@ -474,7 +424,6 @@ FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 FOREIGN_DATA_STRING_INVALID = 1433 -CANT_CREATE_FEDERATED_TABLE = 1434 TRG_IN_WRONG_SCHEMA = 1435 STACK_OVERRUN_NEED_MORE = 1436 TOO_LONG_BODY = 1437 @@ -486,7 +435,6 @@ VIEW_PREVENT_UPDATE = 1443 PS_NO_RECURSION = 1444 SP_CANT_SET_AUTOCOMMIT = 1445 -MALFORMED_DEFINER = 1446 VIEW_FRM_NO_USER = 1447 VIEW_OTHER_USER = 1448 NO_SUCH_USER = 1449 @@ -497,7 +445,6 @@ TRG_NO_DEFINER = 1454 OLD_FILE_FORMAT = 1455 SP_RECURSION_LIMIT = 1456 -SP_PROC_TABLE_CORRUPT = 1457 SP_WRONG_NAME = 1458 TABLE_NEEDS_UPGRADE = 1459 SP_NO_AGGREGATE = 1460 @@ -522,14 +469,10 @@ PARTITION_REQUIRES_VALUES_ERROR = 1479 PARTITION_WRONG_VALUES_ERROR = 1480 PARTITION_MAXVALUE_ERROR = 1481 -PARTITION_SUBPARTITION_ERROR = 1482 -PARTITION_SUBPART_MIX_ERROR = 1483 PARTITION_WRONG_NO_PART_ERROR = 1484 PARTITION_WRONG_NO_SUBPART_ERROR = 1485 WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 -NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 FIELD_NOT_FOUND_PART_ERROR = 1488 -LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 INCONSISTENT_PARTITION_INFO_ERROR = 1490 PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 @@ -562,7 +505,6 @@ CONSECUTIVE_REORG_PARTITIONS = 1519 REORG_OUTSIDE_RANGE = 1520 PARTITION_FUNCTION_FAILURE = 1521 -PART_STATE_ERROR = 1522 LIMITED_PART_RANGE = 1523 PLUGIN_IS_NOT_LOADED = 1524 WRONG_VALUE = 1525 @@ -575,53 +517,30 @@ SIZE_OVERFLOW_ERROR = 1532 ALTER_FILEGROUP_FAILED = 1533 BINLOG_ROW_LOGGING_FAILED = 1534 -BINLOG_ROW_WRONG_TABLE_DEF = 1535 -BINLOG_ROW_RBR_TO_SBR = 1536 EVENT_ALREADY_EXISTS = 1537 -EVENT_STORE_FAILED = 1538 EVENT_DOES_NOT_EXIST = 1539 -EVENT_CANT_ALTER = 1540 -EVENT_DROP_FAILED = 1541 EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 EVENT_ENDS_BEFORE_STARTS = 1543 EVENT_EXEC_TIME_IN_THE_PAST = 1544 -EVENT_OPEN_TABLE_FAILED = 1545 -EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 -COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 -OBSOLETE_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 -CANNOT_LOAD_FROM_TABLE = 1548 -OBSOLETE_CANNOT_LOAD_FROM_TABLE = 1548 -EVENT_CANNOT_DELETE = 1549 -EVENT_COMPILE_ERROR = 1550 EVENT_SAME_NAME = 1551 -EVENT_DATA_TOO_LONG = 1552 DROP_INDEX_FK = 1553 WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 -CANT_WRITE_LOCK_LOG_TABLE = 1555 CANT_LOCK_LOG_TABLE = 1556 -FOREIGN_DUPLICATE_KEY = 1557 FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 -NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 PARTITION_NO_TEMPORARY = 1562 PARTITION_CONST_DOMAIN_ERROR = 1563 PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 -DDL_LOG_ERROR = 1565 NULL_IN_VALUES_LESS_THAN = 1566 WRONG_PARTITION_NAME = 1567 CANT_CHANGE_TX_CHARACTERISTICS = 1568 -CANT_CHANGE_TX_ISOLATION = 1568 DUP_ENTRY_AUTOINCREMENT_CASE = 1569 -EVENT_MODIFY_QUEUE_ERROR = 1570 EVENT_SET_VAR_ERROR = 1571 PARTITION_MERGE_ERROR = 1572 -CANT_ACTIVATE_LOG = 1573 -RBR_NOT_AVAILABLE = 1574 BASE64_DECODE_ERROR = 1575 EVENT_RECURSION_FORBIDDEN = 1576 -EVENTS_DB_ERROR = 1577 ONLY_INTEGERS_ALLOWED = 1578 UNSUPORTED_LOG_ENGINE = 1579 BAD_LOG_STATEMENT = 1580 @@ -634,41 +553,29 @@ BINLOG_PURGE_EMFILE = 1587 EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 -SLAVE_INCIDENT = 1590 NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 BINLOG_UNSAFE_STATEMENT = 1592 -SLAVE_FATAL_ERROR = 1593 -SLAVE_RELAY_LOG_READ_FAILURE = 1594 -SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 -SLAVE_CREATE_EVENT_FAILURE = 1596 -SLAVE_MASTER_COM_FAILURE = 1597 +BINLOG_FATAL_ERROR = 1593 BINLOG_LOGGING_IMPOSSIBLE = 1598 VIEW_NO_CREATION_CTX = 1599 VIEW_INVALID_CREATION_CTX = 1600 -SR_INVALID_CREATION_CTX = 1601 TRG_CORRUPTED_FILE = 1602 TRG_NO_CREATION_CTX = 1603 TRG_INVALID_CREATION_CTX = 1604 EVENT_INVALID_CREATION_CTX = 1605 TRG_CANT_OPEN_TABLE = 1606 -CANT_CREATE_SROUTINE = 1607 -NEVER_USED = 1608 NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 SLAVE_CORRUPT_EVENT = 1610 -LOAD_DATA_INVALID_COLUMN = 1611 LOG_PURGE_NO_FILE = 1612 XA_RBTIMEOUT = 1613 XA_RBDEADLOCK = 1614 NEED_REPREPARE = 1615 -DELAYED_NOT_SUPPORTED = 1616 WARN_NO_MASTER_INFO = 1617 WARN_OPTION_IGNORED = 1618 PLUGIN_DELETE_BUILTIN = 1619 -WARN_PLUGIN_DELETE_BUILTIN = 1619 WARN_PLUGIN_BUSY = 1620 VARIABLE_IS_READONLY = 1621 WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 -SLAVE_HEARTBEAT_FAILURE = 1623 SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 NDB_REPLICATION_SCHEMA_ERROR = 1625 CONFLICT_FN_PARSE_ERROR = 1626 @@ -696,7 +603,6 @@ COND_ITEM_TOO_LONG = 1648 UNKNOWN_LOCALE = 1649 SLAVE_IGNORE_SERVER_IDS = 1650 -QUERY_CACHE_DISABLED = 1651 SAME_NAME_PARTITION_FIELD = 1652 PARTITION_COLUMN_LIST_ERROR = 1653 WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 @@ -714,8 +620,6 @@ BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 BINLOG_UNSAFE_LIMIT = 1668 -BINLOG_UNSAFE_INSERT_DELAYED = 1669 -UNUSED4 = 1669 BINLOG_UNSAFE_SYSTEM_TABLE = 1670 BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 BINLOG_UNSAFE_UDF = 1672 @@ -723,7 +627,6 @@ BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 MESSAGE_AND_STATEMENT = 1676 -SLAVE_CONVERSION_FAILED = 1677 SLAVE_CANT_CREATE_CONVERSION = 1678 INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 PATH_LENGTH = 1680 @@ -746,7 +649,6 @@ VALUES_IS_NOT_INT_TYPE_ERROR = 1697 ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 SET_PASSWORD_AUTH_PLUGIN = 1699 -GRANT_PLUGIN_USER_EXISTS = 1700 TRUNCATE_ILLEGAL_FK = 1701 PLUGIN_IS_PERMANENT = 1702 SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 @@ -773,10 +675,8 @@ BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 TABLE_IN_FK_CHECK = 1725 UNSUPPORTED_ENGINE = 1726 -UNUSED_1 = 1726 BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 CANNOT_LOAD_FROM_TABLE_V2 = 1728 -LAST_MYSQL_ERROR_MESSAGE = 1728 MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 @@ -789,15 +689,11 @@ BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 WARN_INDEX_NOT_APPLICABLE = 1739 PARTITION_EXCHANGE_FOREIGN_KEY = 1740 -NO_SUCH_KEY_VALUE = 1741 RPL_INFO_DATA_TOO_LONG = 1742 -NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 -BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 -NO_SUCH_PARTITION__UNUSED = 1749 CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 @@ -815,24 +711,19 @@ TABLE_HAS_NO_FT = 1764 VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 -GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 -CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL = 1768 SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 -SKIPPING_LOGGED_TRANSACTION = 1771 MALFORMED_GTID_SET_SPECIFICATION = 1772 MALFORMED_GTID_SET_ENCODING = 1773 MALFORMED_GTID_SPECIFICATION = 1774 GNO_EXHAUSTED = 1775 BAD_SLAVE_AUTO_POSITION = 1776 -AUTO_POSITION_REQUIRES_GTID_MODE_ON = 1777 +AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777 CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 -GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 -GTID_MODE_REQUIRES_BINLOG = 1780 +GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 -FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF = 1784 GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 GTID_UNSAFE_CREATE_SELECT = 1786 GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787 @@ -882,12 +773,10 @@ DUP_INDEX = 1831 FK_COLUMN_CANNOT_CHANGE = 1832 FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 -UNUSED5 = 1834 MALFORMED_PACKET = 1835 READ_ONLY_MODE = 1836 -GTID_NEXT_TYPE_UNDEFINED_GROUP = 1837 +GTID_NEXT_TYPE_UNDEFINED_GTID = 1837 VARIABLE_NOT_SETTABLE_IN_SP = 1838 -CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 GTID_PURGED_WAS_CHANGED = 1842 @@ -900,7 +789,6 @@ ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 -UNUSED6 = 1852 ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 @@ -913,7 +801,6 @@ MUST_CHANGE_PASSWORD_LOGIN = 1862 ROW_IN_WRONG_PARTITION = 1863 MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 -INNODB_NO_FT_USES_PARSER = 1865 BINLOG_LOGICAL_CORRUPTION = 1866 WARN_PURGE_LOG_IN_USE = 1867 WARN_PURGE_LOG_IS_ACTIVE = 1868 @@ -932,129 +819,7 @@ INNODB_FORCED_RECOVERY = 1881 AES_INVALID_IV = 1882 PLUGIN_CANNOT_BE_UNINSTALLED = 1883 -GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_GTID_GROUP = 1884 -FILE_CORRUPT = 1885 -ERROR_ON_MASTER = 1886 -INCONSISTENT_ERROR = 1887 -STORAGE_ENGINE_NOT_LOADED = 1888 -GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 1889 -WARN_LEGACY_SYNTAX_CONVERTED = 1890 -BINLOG_UNSAFE_FULLTEXT_PLUGIN = 1891 -CANNOT_DISCARD_TEMPORARY_TABLE = 1892 -FK_DEPTH_EXCEEDED = 1893 -COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 1894 -WARN_TRIGGER_DOESNT_HAVE_CREATED = 1895 -REFERENCED_TRG_DOES_NOT_EXIST = 1896 -EXPLAIN_NOT_SUPPORTED = 1897 -INVALID_FIELD_SIZE = 1898 -MISSING_HA_CREATE_OPTION = 1899 -ENGINE_OUT_OF_MEMORY = 1900 -VCOL_BASED_ON_VCOL = 1900 -PASSWORD_EXPIRE_ANONYMOUS_USER = 1901 -VIRTUAL_COLUMN_FUNCTION_IS_NOT_ALLOWED = 1901 -DATA_CONVERSION_ERROR_FOR_VIRTUAL_COLUMN = 1902 -SLAVE_SQL_THREAD_MUST_STOP = 1902 -NO_FT_MATERIALIZED_SUBQUERY = 1903 -PRIMARY_KEY_BASED_ON_VIRTUAL_COLUMN = 1903 -INNODB_UNDO_LOG_FULL = 1904 -KEY_BASED_ON_GENERATED_VIRTUAL_COLUMN = 1904 -INVALID_ARGUMENT_FOR_LOGARITHM = 1905 -WRONG_FK_OPTION_FOR_VIRTUAL_COLUMN = 1905 -SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 1906 -WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN = 1906 -UNSUPPORTED_ACTION_ON_VIRTUAL_COLUMN = 1907 -WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 1907 -CONST_EXPR_IN_VCOL = 1908 -WARN_ONLY_MASTER_LOG_FILE_NO_POS = 1908 -QUERY_TIMEOUT = 1909 -ROW_EXPR_FOR_VCOL = 1909 -NON_RO_SELECT_DISABLE_TIMER = 1910 -UNSUPPORTED_ENGINE_FOR_VIRTUAL_COLUMNS = 1910 -DUP_LIST_ENTRY = 1911 -UNKNOWN_OPTION = 1911 -BAD_OPTION_VALUE = 1912 -SQL_MODE_NO_EFFECT = 1912 -AGGREGATE_ORDER_FOR_UNION = 1913 -NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1913 -AGGREGATE_ORDER_NON_AGG_QUERY = 1914 -BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1914 -CANT_DO_ONLINE = 1915 -SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 1915 -DATA_OVERFLOW = 1916 -DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER = 1916 -DATA_TRUNCATED = 1917 -SERVER_OFFLINE_MODE = 1917 -BAD_DATA = 1918 -GIS_DIFFERENT_SRIDS = 1918 -DYN_COL_WRONG_FORMAT = 1919 -GIS_UNSUPPORTED_ARGUMENT = 1919 -DYN_COL_IMPLEMENTATION_LIMIT = 1920 -GIS_UNKNOWN_ERROR = 1920 -DYN_COL_DATA = 1921 -GIS_UNKNOWN_EXCEPTION = 1921 -DYN_COL_WRONG_CHARSET = 1922 -GIS_INVALID_DATA = 1922 -BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 1923 -ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES = 1923 -BOOST_GEOMETRY_CENTROID_EXCEPTION = 1924 -QUERY_CACHE_IS_DISABLED = 1924 -BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 1925 -QUERY_CACHE_IS_GLOBALY_DISABLED = 1925 -BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 1926 -VIEW_ORDERBY_IGNORED = 1926 -BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 1927 -CONNECTION_KILLED = 1927 -BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 1928 -INTERNAL_ERROR = 1928 -INSIDE_TRANSACTION_PREVENTS_SWITCH_SKIP_REPLICATION = 1929 -STD_BAD_ALLOC_ERROR = 1929 -STD_DOMAIN_ERROR = 1930 -STORED_FUNCTION_PREVENTS_SWITCH_SKIP_REPLICATION = 1930 -QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT = 1931 -STD_LENGTH_ERROR = 1931 -NO_SUCH_TABLE_IN_ENGINE = 1932 -STD_INVALID_ARGUMENT = 1932 -GEOMETRY_SRID_MISMATCH = 1933 -STD_OUT_OF_RANGE_ERROR = 1933 -NO_SUCH_SPATIAL_REF_ID = 1934 -STD_OVERFLOW_ERROR = 1934 -STD_RANGE_ERROR = 1935 -STD_UNDERFLOW_ERROR = 1936 -STD_LOGIC_ERROR = 1937 -STD_RUNTIME_ERROR = 1938 -STD_UNKNOWN_EXCEPTION = 1939 -GIS_DATA_WRONG_ENDIANESS = 1940 -CHANGE_MASTER_PASSWORD_LENGTH = 1941 -USER_LOCK_WRONG_NAME = 1942 -USER_LOCK_DEADLOCK = 1943 -REPLACE_INACCESSIBLE_ROWS = 1944 -ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 1945 -ILLEGAL_USER_VAR = 1946 -GTID_MODE_OFF = 1947 -UNSUPPORTED_BY_REPLICATION_THREAD = 1948 -INCORRECT_TYPE = 1949 -FIELD_IN_ORDER_NOT_SELECT = 1950 -AGGREGATE_IN_ORDER_NOT_SELECT = 1951 -INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 1952 -NET_OK_PACKET_TOO_LARGE = 1953 -INVALID_JSON_DATA = 1954 -INVALID_GEOJSON_MISSING_MEMBER = 1955 -INVALID_GEOJSON_WRONG_TYPE = 1956 -INVALID_GEOJSON_UNSPECIFIED = 1957 -DIMENSION_UNSUPPORTED = 1958 -SLAVE_CHANNEL_DOES_NOT_EXIST = 1959 -SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 1960 -SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 1961 -SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 1962 -SLAVE_CHANNEL_DELETE = 1963 -SLAVE_MULTIPLE_CHANNELS_CMD = 1964 -SLAVE_MAX_CHANNELS_EXCEEDED = 1965 -SLAVE_CHANNEL_MUST_STOP = 1966 -SLAVE_CHANNEL_NOT_RUNNING = 1967 -SLAVE_CHANNEL_WAS_RUNNING = 1968 -SLAVE_CHANNEL_WAS_NOT_RUNNING = 1969 -SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 1970 -SLAVE_CHANNEL_SQL_SKIP_COUNTER = 1971 -WRONG_FIELD_WITH_GROUP_V2 = 1972 -MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 1973 +GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884 +SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885 +MISSING_KEY = 1886 ERROR_LAST = 1973 From aca3a396c766dfc36c83ae8cbf955c08bd4c1286 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 12 Dec 2018 17:51:48 +0900 Subject: [PATCH 158/396] Remove old TIMESTAMP format for MySQL<4.1 (#308) --- MySQLdb/converters.py | 2 +- MySQLdb/times.py | 14 -------------- tests/test_MySQLdb_times.py | 12 ------------ 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 7b1eb035..f0d01786 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -117,7 +117,7 @@ def quote_tuple(t, d): FIELD_TYPE.LONGLONG: int, FIELD_TYPE.INT24: int, FIELD_TYPE.YEAR: int, - FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter, + FIELD_TYPE.TIMESTAMP: DateTime_or_None, FIELD_TYPE.DATETIME: DateTime_or_None, FIELD_TYPE.TIME: TimeDelta_or_None, FIELD_TYPE.DATE: Date_or_None, diff --git a/MySQLdb/times.py b/MySQLdb/times.py index a7eaa53b..d47c8fb8 100644 --- a/MySQLdb/times.py +++ b/MySQLdb/times.py @@ -129,17 +129,3 @@ def DateTime2literal(d, c): def DateTimeDelta2literal(d, c): """Format a DateTimeDelta object as a time.""" return string_literal(format_TIMEDELTA(d)) - -def mysql_timestamp_converter(s): - """Convert a MySQL TIMESTAMP to a Timestamp object.""" - # MySQL>4.1 returns TIMESTAMP in the same format as DATETIME - if s[4] == '-': return DateTime_or_None(s) - s = s + "0"*(14-len(s)) # padding - parts = map(int, filter(None, (s[:4],s[4:6],s[6:8], - s[8:10],s[10:12],s[12:14]))) - try: - return Timestamp(*parts) - except (SystemExit, KeyboardInterrupt): - raise # pragma: no cover - except: - return None diff --git a/tests/test_MySQLdb_times.py b/tests/test_MySQLdb_times.py index 8000f645..d9d3e027 100644 --- a/tests/test_MySQLdb_times.py +++ b/tests/test_MySQLdb_times.py @@ -81,18 +81,6 @@ def test_timestamp_from_ticks(self, mock): assert times.TimestampFromTicks(1430000000.123) == datetime(2015, 4, 25, 22, 13, 20) -class TestTimestampConverter(unittest.TestCase): - def test_mysql_timestamp_converter(self): - assert times.mysql_timestamp_converter('2015-12-13') == date(2015, 12, 13) - assert times.mysql_timestamp_converter('2038-01-19 03:14:07') == datetime(2038, 1, 19, 3, 14, 7) - - assert times.mysql_timestamp_converter('2015121310') == datetime(2015, 12, 13, 10, 0) - assert times.mysql_timestamp_converter('20151213101112') == datetime(2015, 12, 13, 10, 11, 12) - - assert times.mysql_timestamp_converter('20151313') is None - assert times.mysql_timestamp_converter('2015-13-13') is None - - class TestToLiteral(unittest.TestCase): def test_datetime_to_literal(self): assert times.DateTime2literal(datetime(2015, 12, 13), '') == b"'2015-12-13 00:00:00'" From bf77dd06338592832e70812ec813382f805ef167 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 12 Dec 2018 20:25:12 +0900 Subject: [PATCH 159/396] Update FIELD_TYPEs (#309) --- MySQLdb/__init__.py | 3 +-- MySQLdb/constants/FIELD_TYPE.py | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index fa52a83d..92b5d264 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -37,7 +37,6 @@ from sets import ImmutableSet as frozenset class DBAPISet(frozenset): - """A special type of set for which A == x is true if A is a DBAPISet and x is a member of that set.""" @@ -54,7 +53,7 @@ def __eq__(self, other): NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, FIELD_TYPE.TINY, FIELD_TYPE.YEAR, FIELD_TYPE.NEWDECIMAL]) -DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE]) +DATE = DBAPISet([FIELD_TYPE.DATE]) TIME = DBAPISet([FIELD_TYPE.TIME]) TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) DATETIME = TIMESTAMP diff --git a/MySQLdb/constants/FIELD_TYPE.py b/MySQLdb/constants/FIELD_TYPE.py index 2a307644..3c4eca9f 100644 --- a/MySQLdb/constants/FIELD_TYPE.py +++ b/MySQLdb/constants/FIELD_TYPE.py @@ -18,9 +18,13 @@ TIME = 11 DATETIME = 12 YEAR = 13 -NEWDATE = 14 +# NEWDATE = 14 # Internal to MySQL. VARCHAR = 15 BIT = 16 +# TIMESTAMP2 = 17 +# DATETIME2 = 18 +# TIME2 = 19 +JSON = 245 NEWDECIMAL = 246 ENUM = 247 SET = 248 From fe8032430c3642078fefc4cb7661cc192cfe4ea6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 13 Dec 2018 12:41:53 +0900 Subject: [PATCH 160/396] Reimplement JSON support (#310) --- MySQLdb/_mysql.c | 19 ++++--------------- MySQLdb/connections.py | 2 +- MySQLdb/converters.py | 1 + 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 53e57f85..84714170 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1138,7 +1138,7 @@ _mysql_field_to_python( v = PyUnicode_Decode(rowitem, length, encoding, NULL); } } - else if (converter == (PyObject*)&PyBytes_Type) { + else if (converter == (PyObject*)&PyBytes_Type || converter == Py_None) { //fprintf(stderr, "decoding with bytes\n", encoding); v = PyBytes_FromStringAndSize(rowitem, length); } @@ -1146,7 +1146,7 @@ _mysql_field_to_python( //fprintf(stderr, "decoding with int\n", encoding); v = PyInt_FromString(rowitem, NULL, 10); } - else if (converter != Py_None) { + else { //fprintf(stderr, "decoding with callback\n"); //PyObject_Print(converter, stderr, 0); //fprintf(stderr, "\n"); @@ -1158,17 +1158,7 @@ _mysql_field_to_python( #endif rowitem, (int)length); - } else { - //fprintf(stderr, "converter=None\n"); -#ifdef IS_PY3K - if (!binary) { - v = PyUnicode_FromStringAndSize(rowitem, (int)length); - } else -#endif - v = PyBytes_FromStringAndSize(rowitem, (int)length); } - if (!v) - return NULL; } else { Py_INCREF(Py_None); v = Py_None; @@ -1414,7 +1404,7 @@ _mysql_ConnectionObject_change_user( { char *user, *pwd=NULL, *db=NULL; int r; - static char *kwlist[] = { "user", "passwd", "db", NULL } ; + static char *kwlist[] = { "user", "passwd", "db", NULL } ; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", kwlist, &user, &pwd, &db)) @@ -1424,8 +1414,7 @@ _mysql_ConnectionObject_change_user( r = mysql_change_user(&(self->connection), user, pwd, db); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_character_set_name__doc__[] = diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index e902912f..29cc4f32 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -183,7 +183,7 @@ def unicode_literal(u, dummy=None): if use_unicode: for t in (FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING, FIELD_TYPE.VARCHAR, FIELD_TYPE.TINY_BLOB, - FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB): + FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB, FIELD_TYPE.JSON): self.converter[t] = _bytes_or_str self.encoders[unicode] = unicode_literal diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index f0d01786..9b9cb8be 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -129,4 +129,5 @@ def quote_tuple(t, d): FIELD_TYPE.STRING: bytes, FIELD_TYPE.VAR_STRING: bytes, FIELD_TYPE.VARCHAR: bytes, + FIELD_TYPE.JSON: bytes, } From aca63e5d1c749881c27b5d3ea478d3150e619fbe Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 13 Dec 2018 12:44:25 +0900 Subject: [PATCH 161/396] Use Py_RETURN_NONE --- MySQLdb/_mysql.c | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 84714170..08ea9ae0 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -664,8 +664,7 @@ _mysql_debug( char *debug; if (!PyArg_ParseTuple(args, "s", &debug)) return NULL; mysql_debug(debug); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_dump_debug_info__doc__[] = @@ -685,8 +684,7 @@ _mysql_ConnectionObject_dump_debug_info( err = mysql_dump_debug_info(&(self->connection)); Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_autocommit__doc__[] = @@ -704,8 +702,7 @@ _mysql_ConnectionObject_autocommit( err = mysql_autocommit(&(self->connection), flag); Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_get_autocommit__doc__[] = @@ -737,8 +734,7 @@ _mysql_ConnectionObject_commit( err = mysql_commit(&(self->connection)); Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_rollback__doc__[] = @@ -1591,8 +1587,7 @@ _mysql_ConnectionObject_info( check_connection(self); s = mysql_info(&(self->connection)); if (s) return PyString_FromString(s); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_insert_id__doc__[] = @@ -1646,8 +1641,7 @@ _mysql_ConnectionObject_kill( r = mysql_kill(&(self->connection), pid); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_field_count__doc__[] = @@ -1741,8 +1735,7 @@ _mysql_ConnectionObject_ping( r = mysql_ping(&(self->connection)); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_query__doc__[] = @@ -1765,8 +1758,7 @@ _mysql_ConnectionObject_query( r = mysql_real_query(&(self->connection), query, len); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -1789,8 +1781,7 @@ _mysql_ConnectionObject_send_query( r = mysql_send_query(mysql, query, len); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -1810,8 +1801,7 @@ _mysql_ConnectionObject_read_query_result( r = (int)mysql_read_query_result(mysql); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_select_db__doc__[] = @@ -1839,8 +1829,7 @@ _mysql_ConnectionObject_select_db( r = mysql_select_db(&(self->connection), db); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_shutdown__doc__[] = @@ -1859,8 +1848,7 @@ _mysql_ConnectionObject_shutdown( r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static char _mysql_ConnectionObject_stat__doc__[] = @@ -2021,8 +2009,7 @@ _mysql_ResultObject_data_seek( if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; check_result_connection(self); mysql_data_seek(self->result, row); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static void From 4b40c8ce056a9633c3ca2738ce59b821d918ac26 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 13 Dec 2018 16:10:58 +0900 Subject: [PATCH 162/396] Remove _mysql.thread_safe() (#311) --- MySQLdb/_mysql.c | 16 ---------------- doc/user_guide.rst | 1 - tests/test_MySQLdb_nonstandard.py | 3 --- 3 files changed, 20 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 08ea9ae0..e90d45eb 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -225,16 +225,6 @@ _get_encoding(MYSQL *mysql) return cs.csname; } -static char _mysql_thread_safe__doc__[] = -"Indicates whether the client is compiled as thread-safe."; - -static PyObject *_mysql_thread_safe( - PyObject *self, - PyObject *noargs) -{ - return PyInt_FromLong((long)mysql_thread_safe()); -} - static char _mysql_ResultObject__doc__[] = "result(connection, use=0, converter={}) -- Result set from a query.\n\ \n\ @@ -2585,12 +2575,6 @@ _mysql_methods[] = { METH_NOARGS, _mysql_get_client_info__doc__ }, - { - "thread_safe", - (PyCFunction)_mysql_thread_safe, - METH_NOARGS, - _mysql_thread_safe__doc__ - }, {NULL, NULL} /* sentinel */ }; diff --git a/doc/user_guide.rst b/doc/user_guide.rst index c83c3f3f..173918dd 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -89,7 +89,6 @@ MySQL C API function mapping ``mysql_stat()`` ``conn.stat()`` ``mysql_store_result()`` ``conn.store_result()`` ``mysql_thread_id()`` ``conn.thread_id()`` - ``mysql_thread_safe_client()`` ``conn.thread_safe_client()`` ``mysql_use_result()`` ``conn.use_result()`` ``mysql_warning_count()`` ``conn.warning_count()`` ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index 55cfc630..2b33c398 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -35,9 +35,6 @@ def test_version(self): def test_client_info(self): self.assertTrue(isinstance(_mysql.get_client_info(), str)) - def test_thread_safe(self): - self.assertTrue(isinstance(_mysql.thread_safe(), int)) - def test_escape_string(self): self.assertEqual(_mysql.escape_string(b'foo"bar'), b'foo\\"bar', "escape byte string") From 0b6c6656f262409139fcd430a5a5e44883251970 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 13 Dec 2018 17:40:37 +0900 Subject: [PATCH 163/396] Refactoring JSON support (#312) --- MySQLdb/_mysql.c | 106 ++++++++++++++++++++++------------------- MySQLdb/connections.py | 5 +- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index e90d45eb..b0ba5310 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -302,10 +302,13 @@ _mysql_ResultObject_Initialize( long flags = fields[i].flags; PyObject *fun2=NULL; int j, n2=PySequence_Size(fun); - if (fields[i].charsetnr != 63) { /* maaagic */ - flags &= ~BINARY_FLAG; - } else { + // BINARY_FLAG means ***_bin collation is used. + // To distinguish text and binary, we shoud use charsetnr==63 (binary). + // But we abuse BINARY_FLAG for historical reason. + if (fields[i].charsetnr == 63) { flags |= BINARY_FLAG; + } else { + flags &= ~BINARY_FLAG; } for (j=0; jtype; - // Return bytes for binary and string types. - int binary = 0; - if (field_type == FIELD_TYPE_TINY_BLOB || - field_type == FIELD_TYPE_MEDIUM_BLOB || - field_type == FIELD_TYPE_LONG_BLOB || - field_type == FIELD_TYPE_BLOB || - field_type == FIELD_TYPE_VAR_STRING || - field_type == FIELD_TYPE_STRING || - field_type == FIELD_TYPE_GEOMETRY || - field_type == FIELD_TYPE_BIT) { - binary = 1; + if (rowitem == NULL) { + Py_RETURN_NONE; } -#endif - if (rowitem) { - if (converter == (PyObject*)&PyUnicode_Type) { - if (encoding == utf8) { - //fprintf(stderr, "decoding with utf8!\n"); - v = PyUnicode_DecodeUTF8(rowitem, length, NULL); - } else { - //fprintf(stderr, "decoding with %s\n", encoding); - v = PyUnicode_Decode(rowitem, length, encoding, NULL); - } - } - else if (converter == (PyObject*)&PyBytes_Type || converter == Py_None) { - //fprintf(stderr, "decoding with bytes\n", encoding); - v = PyBytes_FromStringAndSize(rowitem, length); - } - else if (converter == (PyObject*)&PyInt_Type) { - //fprintf(stderr, "decoding with int\n", encoding); - v = PyInt_FromString(rowitem, NULL, 10); + + // Fast paths for int, string and binary. + if (converter == (PyObject*)&PyUnicode_Type) { + if (encoding == utf8) { + //fprintf(stderr, "decoding with utf8!\n"); + return PyUnicode_DecodeUTF8(rowitem, length, NULL); + } else { + //fprintf(stderr, "decoding with %s\n", encoding); + return PyUnicode_Decode(rowitem, length, encoding, NULL); } - else { - //fprintf(stderr, "decoding with callback\n"); - //PyObject_Print(converter, stderr, 0); - //fprintf(stderr, "\n"); - v = PyObject_CallFunction(converter, + } + if (converter == (PyObject*)&PyBytes_Type || converter == Py_None) { + //fprintf(stderr, "decoding with bytes\n", encoding); + return PyBytes_FromStringAndSize(rowitem, length); + } + if (converter == (PyObject*)&PyInt_Type) { + //fprintf(stderr, "decoding with int\n", encoding); + return PyInt_FromString(rowitem, NULL, 10); + } + + //fprintf(stderr, "decoding with callback\n"); + //PyObject_Print(converter, stderr, 0); + //fprintf(stderr, "\n"); #ifdef IS_PY3K - binary ? "y#" : "s#", + int binary; + switch (field->type) { + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + case FIELD_TYPE_BLOB: + case FIELD_TYPE_VAR_STRING: + case FIELD_TYPE_STRING: + case FIELD_TYPE_GEOMETRY: + case FIELD_TYPE_BIT: +#ifdef FIELD_TYPE_JSON + case FIELD_TYPE_JSON: #else - "s#", + case 245: // JSON #endif - rowitem, - (int)length); - } - } else { - Py_INCREF(Py_None); - v = Py_None; + // Call converter with bytes + binary = 1; + default: // e.g. FIELD_TYPE_DATETIME, etc. + // Call converter with unicode string + binary = 0; } - return v; + return PyObject_CallFunction(converter, + binary ? "y#" : "s#", + rowitem, (int)length); +#else + return PyObject_CallFunction(converter, + "s#", rowitem, (int)length); +#endif } static PyObject * @@ -1226,7 +1232,7 @@ _mysql_row_to_dict_old( unsigned int n, i; unsigned long *length; PyObject *r, *c; - MYSQL_FIELD *fields; + MYSQL_FIELD *fields; n = mysql_num_fields(self->result); if (!(r = PyDict_New())) return NULL; diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 29cc4f32..7de8c8c2 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -183,8 +183,11 @@ def unicode_literal(u, dummy=None): if use_unicode: for t in (FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING, FIELD_TYPE.VARCHAR, FIELD_TYPE.TINY_BLOB, - FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB, FIELD_TYPE.JSON): + FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB): self.converter[t] = _bytes_or_str + # Unlike other string/blob types, JSON is always text. + # MySQL may return JSON with charset==binary. + self.converter[FIELD_TYPE.JSON] = unicode self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS From 911bef988c49fbfdc59e44f9198a57e9357dd4ff Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 13 Dec 2018 20:23:46 +0900 Subject: [PATCH 164/396] Refactor extension module initialization (#313) --- MySQLdb/_mysql.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index b0ba5310..1f1b299e 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -2468,7 +2468,7 @@ PyTypeObject _mysql_ConnectionObject_Type = { 0, /* (long) tp_dictoffset */ (initproc)_mysql_ConnectionObject_Initialize, /* tp_init */ NULL, /* tp_alloc */ - NULL, /* tp_new */ + PyType_GenericNew, /* tp_new */ NULL, /* tp_free Low-level free-memory routine */ 0, /* (PyObject *) tp_bases */ 0, /* (PyObject *) tp_mro method resolution order */ @@ -2536,7 +2536,7 @@ PyTypeObject _mysql_ResultObject_Type = { 0, /* (long) tp_dictoffset */ (initproc)_mysql_ResultObject_Initialize, /* tp_init */ NULL, /* tp_alloc */ - NULL, /* tp_new */ + PyType_GenericNew, /* tp_new */ NULL, /* tp_free Low-level free-memory routine */ 0, /* (PyObject *) tp_bases */ 0, /* (PyObject *) tp_mro method resolution order */ @@ -2635,14 +2635,6 @@ init_mysql(void) { PyObject *dict, *module, *emod, *edict; -#ifndef IS_PY3K - _mysql_ConnectionObject_Type.ob_type = &PyType_Type; - _mysql_ResultObject_Type.ob_type = &PyType_Type; -#endif - _mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc; - _mysql_ConnectionObject_Type.tp_new = PyType_GenericNew; - _mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc; - _mysql_ResultObject_Type.tp_new = PyType_GenericNew; #ifdef IS_PY3K if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) return NULL; @@ -2652,8 +2644,10 @@ init_mysql(void) module = PyModule_Create(&_mysqlmodule); if (!module) return module; /* this really should never happen */ #else - _mysql_ConnectionObject_Type.tp_free = _PyObject_GC_Del; - _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; + if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) + return; + if (PyType_Ready(&_mysql_ResultObject_Type) < 0) + return; module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, (PyObject *)NULL, PYTHON_API_VERSION); if (!module) return; /* this really should never happen */ From 8ad1525c3f11ac299cdf94e9a6b7f6244d37ee7f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 14 Dec 2018 16:46:30 +0900 Subject: [PATCH 165/396] Raise ProgrammingError for nan and inf (#314) * Raise ProgrammingError when inf or nan is passed Fixes #246 * Rename _mysql_exceptions -> _exceptions --- MySQLdb/{_mysql_exceptions.py => _exceptions.py} | 10 +++------- MySQLdb/_mysql.c | 2 +- MySQLdb/compat.py | 2 ++ MySQLdb/connections.py | 2 +- MySQLdb/converters.py | 3 +++ MySQLdb/cursors.py | 4 ++-- doc/{_mysql_exceptions.rst => _exceptions.rst} | 4 ++-- doc/_mysql.rst | 2 +- doc/user_guide.rst | 8 ++++---- metadata.cfg | 2 +- 10 files changed, 20 insertions(+), 19 deletions(-) rename MySQLdb/{_mysql_exceptions.py => _exceptions.py} (91%) rename doc/{_mysql_exceptions.rst => _exceptions.rst} (58%) diff --git a/MySQLdb/_mysql_exceptions.py b/MySQLdb/_exceptions.py similarity index 91% rename from MySQLdb/_mysql_exceptions.py rename to MySQLdb/_exceptions.py index 99a79d77..0f14f3bd 100644 --- a/MySQLdb/_mysql_exceptions.py +++ b/MySQLdb/_exceptions.py @@ -1,15 +1,10 @@ -"""_mysql_exceptions: Exception classes for _mysql and MySQLdb. +"""Exception classes for _mysql and MySQLdb. These classes are dictated by the DB API v2.0: https://www.python.org/dev/peps/pep-0249/ """ - -try: - from exceptions import Exception, StandardError, Warning -except ImportError: - # Python 3 - StandardError = Exception +from .compat import StandardError class MySQLError(StandardError): @@ -20,6 +15,7 @@ class Warning(Warning, MySQLError): """Exception raised for important warnings like data truncations while inserting, etc.""" + class Error(MySQLError): """Exception that is the base class of all other error exceptions (not Warning).""" diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 1f1b299e..5ae6652c 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -2669,7 +2669,7 @@ init_mysql(void) (PyObject *)&_mysql_ResultObject_Type)) goto error; Py_INCREF(&_mysql_ResultObject_Type); - if (!(emod = PyImport_ImportModule("MySQLdb._mysql_exceptions"))) { + if (!(emod = PyImport_ImportModule("MySQLdb._exceptions"))) { PyErr_Print(); goto error; } diff --git a/MySQLdb/compat.py b/MySQLdb/compat.py index 8fe6709a..f8d98ac2 100644 --- a/MySQLdb/compat.py +++ b/MySQLdb/compat.py @@ -5,8 +5,10 @@ unicode = unicode unichr = unichr long = long + StandardError = StandardError else: PY2 = False unicode = str unichr = chr long = int + StandardError = Exception diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 7de8c8c2..c7c4c14f 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -9,7 +9,7 @@ from MySQLdb import cursors, _mysql from MySQLdb.compat import unicode, PY2 -from MySQLdb._mysql_exceptions import ( +from MySQLdb._exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, NotSupportedError, ProgrammingError, diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 9b9cb8be..4c4a1a1b 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -36,6 +36,7 @@ from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * from MySQLdb.compat import PY2, long, unicode +from MySQLdb._exceptions import ProgrammingError NoneType = type(None) @@ -66,6 +67,8 @@ def Unicode2Str(s, d): def Float2Str(o, d): s = repr(o) + if s in ('inf', 'nan'): + raise ProgrammingError("%s can not be used with MySQL" % s) if 'e' not in s: s += 'e0' return s diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 9a5e76f9..b8c0d886 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -9,7 +9,7 @@ import sys from .compat import unicode -from ._mysql_exceptions import ( +from ._exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, NotSupportedError, ProgrammingError) @@ -48,7 +48,7 @@ class BaseCursor(object): #: Default value of max_allowed_packet is 1048576. max_stmt_length = 64*1024 - from ._mysql_exceptions import ( + from ._exceptions import ( MySQLError, Warning, Error, InterfaceError, DatabaseError, DataError, OperationalError, IntegrityError, InternalError, ProgrammingError, NotSupportedError, diff --git a/doc/_mysql_exceptions.rst b/doc/_exceptions.rst similarity index 58% rename from doc/_mysql_exceptions.rst rename to doc/_exceptions.rst index 2d43525c..b509338c 100644 --- a/doc/_mysql_exceptions.rst +++ b/doc/_exceptions.rst @@ -1,7 +1,7 @@ -_mysql_exceptions Module +_exceptions Module ======================== -.. automodule:: _mysql_exceptions +.. automodule:: MySQLdb._exceptions :members: :undoc-members: :show-inheritance: diff --git a/doc/_mysql.rst b/doc/_mysql.rst index 7ac4918d..cf464b4f 100644 --- a/doc/_mysql.rst +++ b/doc/_mysql.rst @@ -1,7 +1,7 @@ _mysql Module ============= -.. automodule:: _mysql +.. automodule:: MySQLdb._mysql :members: :undoc-members: :show-inheritance: diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 173918dd..7c4302b4 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -106,7 +106,7 @@ Okay, so you want to use ``_mysql`` anyway. Here are some examples. The simplest possible database connection is:: - import _mysql + from MySQLdb import _mysql db=_mysql.connect() This creates a connection to the MySQL server running on the local @@ -162,8 +162,8 @@ substitution, so you have to pass a complete query string to WHERE price < 5""") There's no return value from this, but exceptions can be raised. The -exceptions are defined in a separate module, ``_mysql_exceptions``, -but ``_mysql`` exports them. Read DB API specification PEP-249_ to +exceptions are defined in a separate module, ``MySQLdb._exceptions``, +but ``MySQLdb._mysql`` exports them. Read DB API specification PEP-249_ to find out what they are, or you can use the catch-all ``MySQLError``. .. _PEP-249: https://www.python.org/dev/peps/pep-0249/ @@ -213,7 +213,7 @@ implicitly asked for one row, since we didn't specify ``maxrows``. The other oddity is: Assuming these are numeric columns, why are they returned as strings? Because MySQL returns all data as strings and expects you to convert it yourself. This would be a real pain in the -ass, but in fact, ``_mysql`` can do this for you. (And ``MySQLdb`` +ass, but in fact, ``MySQLdb._mysql`` can do this for you. (And ``MySQLdb`` does do this for you.) To have automatic type conversion done, you need to create a type converter dictionary, and pass this to ``connect()`` as the ``conv`` keyword parameter. diff --git a/metadata.cfg b/metadata.cfg index 589e52f7..8d185a81 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -28,7 +28,7 @@ classifiers: Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: - MySQLdb._mysql_exceptions + MySQLdb._exceptions MySQLdb.compat MySQLdb.connections MySQLdb.converters From 74c16e7afa79bf5c8b1ef45546502c9839910797 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 14 Dec 2018 17:24:15 +0900 Subject: [PATCH 166/396] Remove unused converters (#315) --- MySQLdb/converters.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 4c4a1a1b..645e3780 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -59,12 +59,6 @@ def Thing2Str(s, d): """Convert something into a string via str().""" return str(s) -def Unicode2Str(s, d): - """Convert a unicode object to a string using the default encoding. - This is only used as a placeholder for the real function, which - is connection-dependent.""" - return s.encode() - def Float2Str(o, d): s = repr(o) if s in ('inf', 'nan'): @@ -90,9 +84,6 @@ def Decimal2Literal(o, d): def array2Str(o, d): return Thing2Literal(o.tostring(), d) -def quote_tuple(t, d): - return "(%s)" % (','.join(escape_sequence(t, d))) - # bytes or str regarding to BINARY_FLAG. _bytes_or_str = ((FLAG.BINARY, bytes), (None, unicode)) @@ -106,7 +97,6 @@ def quote_tuple(t, d): Date: Thing2Literal, DateTimeType: DateTime2literal, DateTimeDeltaType: DateTimeDelta2literal, - str: Thing2Literal, # default set: Set2Str, Decimal: Decimal2Literal, From ecf6d53aef525ce2f7dfd4bdbf70fd1b470d6b19 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 15 Dec 2018 10:15:13 +0900 Subject: [PATCH 167/396] 1.4.0rc2 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 8d185a81..431e6014 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.0rc1 -version_info: (1,4,0,'rc',1) +version: 1.4.0rc2 +version_info: (1,4,0,'rc',2) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From ea80e8f07a803307778fa43ae8f70f28f39a4a5b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 17 Dec 2018 19:27:54 +0900 Subject: [PATCH 168/396] Fix error handling of mysql_real_connect (#317) Fixes #316 --- MySQLdb/_mysql.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 5ae6652c..cf4566d6 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -446,6 +446,7 @@ _mysql_ConnectionObject_Initialize( Py_BEGIN_ALLOW_THREADS ; conn = mysql_init(&(self->connection)); + self->open = 1; if (connect_timeout) { unsigned int timeout = connect_timeout; mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, @@ -496,6 +497,7 @@ _mysql_ConnectionObject_Initialize( if (!conn) { _mysql_Exception(self); + self->open = 0; return -1; } @@ -515,7 +517,6 @@ _mysql_ConnectionObject_Initialize( be done here. tp_dealloc still needs to call PyObject_GC_UnTrack(), however. */ - self->open = 1; return 0; } From f7544710979033523a7009e64da05c8fa5c7a9d0 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 17 Dec 2018 19:28:16 +0900 Subject: [PATCH 169/396] Update HISTORY --- HISTORY.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 54b09a53..47356b7f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -26,11 +26,18 @@ Release: TBD * Remove ``_mysql.NULL`` constant. +* Remove ``_mysql.thread_safe()`` function. + * Support non-ASCII field name with non-UTF-8 connection encoding. (#210) * Optimize decoding speed of string and integer types. -* Removed ``MySQLdb.constants.REFRESH`` module. +* Remove ``MySQLdb.constants.REFRESH`` module. + +* Remove support for old datetime format for MySQL < 4.1. + +* Fix wrong errno is raised when ``mysql_real_connect`` is failed. (#316) + ====================== What's new in 1.3.14 From 0d1b6b6dc98d388abbf18bc4b144018559f95a43 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 17 Dec 2018 19:28:47 +0900 Subject: [PATCH 170/396] 1.4.0rc3 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 431e6014..c3fddb50 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.0rc2 -version_info: (1,4,0,'rc',2) +version: 1.4.0rc3 +version_info: (1,4,0,'rc',3) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 3870135037e602f2efd680e7a43b47f0ee9efd3e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 18 Jan 2019 20:52:10 +0900 Subject: [PATCH 171/396] Fix windows build (#322) --- MySQLdb/_mysql.c | 13 ++++++------- setup_windows.py | 23 +++++++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index cf4566d6..45df37c3 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -917,7 +917,8 @@ _mysql_string_literal( { PyObject *str, *s; char *in, *out; - int len, size; + unsigned long len; + Py_ssize_t size; if (self && PyModule_Check((PyObject*)self)) self = NULL; @@ -1986,12 +1987,10 @@ _mysql_ConnectionObject_repr( { char buf[300]; if (self->open) - sprintf(buf, "<_mysql.connection open to '%.256s' at %lx>", - self->connection.host, - (long)self); + snprintf(buf, 300, "<_mysql.connection open to '%.256s' at %p>", + self->connection.host, self); else - sprintf(buf, "<_mysql.connection closed at %lx>", - (long)self); + snprintf(buf, 300, "<_mysql.connection closed at %p>", self); return PyString_FromString(buf); } @@ -2024,7 +2023,7 @@ _mysql_ResultObject_repr( _mysql_ResultObject *self) { char buf[300]; - sprintf(buf, "<_mysql.result object at %lx>", (long)self); + snprintf(buf, 300, "<_mysql.result object at %p>", self); return PyString_FromString(buf); } diff --git a/setup_windows.py b/setup_windows.py index 5a4d236b..cb2cbab0 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -11,21 +11,24 @@ def get_config(): extra_objects = [] - if enabled(options, 'embedded'): - client = "mysqld" - else: - client = "mysqlclient" + # client = "mysqlclient" + client = "mariadbclient" vcversion = int(get_build_version()) - library_dirs = [ os.path.join(connector, r'lib\vs%d' % vcversion) ] - libraries = [ 'kernel32', 'advapi32', 'wsock32', client ] - include_dirs = [ os.path.join(connector, r'include') ] - extra_compile_args = [ '/Zl' ] + if client == "mariadbclient": + library_dirs = [os.path.join(connector, 'lib', 'mariadb')] + libraries = ['kernel32', 'advapi32', 'wsock32', 'shlwapi', 'Ws2_32', client ] + include_dirs = [os.path.join(connector, 'include', 'mariadb')] + else: + library_dirs = [os.path.join(connector, r'lib\vs%d' % vcversion), + os.path.join(connector, "lib")] + libraries = ['kernel32', 'advapi32', 'wsock32', client ] + include_dirs = [os.path.join(connector, r'include')] + + extra_compile_args = ['/Zl', '/D_CRT_SECURE_NO_WARNINGS' ] extra_link_args = ['/MANIFEST'] name = "mysqlclient" - if enabled(options, 'embedded'): - name = name + "-embedded" metadata['name'] = name define_macros = [ From bf0ef58baae90f2f76d88c404025fde83f572fdb Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 18 Jan 2019 20:55:42 +0900 Subject: [PATCH 172/396] 1.4.0 --- HISTORY.rst | 2 +- metadata.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 47356b7f..3fe5212d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,7 +2,7 @@ What's new in 1.4.0 ====================== -Release: TBD +Release: 2019-01-18 * Removed ``threadsafe`` and ``embedded`` build options. diff --git a/metadata.cfg b/metadata.cfg index c3fddb50..e3685b88 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.0rc3 -version_info: (1,4,0,'rc',3) +version: 1.4.0 +version_info: (1,4,0,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From f48a294aaae8199c25d8f8e4c0a22e14c9bab415 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 19 Jan 2019 12:55:41 +0900 Subject: [PATCH 173/396] Fix dict parameter support (#324) --- MySQLdb/cursors.py | 7 ++++++- tests/test_cursor.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index b8c0d886..4460799e 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -181,7 +181,12 @@ def execute(self, query, args=None): if args is not None: if isinstance(args, dict): - args = dict((key, db.literal(item)) for key, item in args.items()) + nargs = {} + for key, item in args.items(): + if isinstance(key, unicode): + key = key.encode(db.encoding) + nargs[key] = db.literal(item) + args = nargs else: args = tuple(map(db.literal, args)) try: diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 7919b561..ff96368f 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,3 +1,5 @@ +from __future__ import print_function, absolute_import + import pytest import MySQLdb.cursors from configdb import connection_factory @@ -76,3 +78,13 @@ def test_executemany(): assert cursor._executed.endswith(b"(3, 4),(5, 6)"), "executemany with %% not in one query" finally: cursor.execute("DROP TABLE IF EXISTS percent_test") + + +def test_pyparam(): + conn = connect() + cursor = conn.cursor() + + cursor.execute(u"SELECT %(a)s, %(b)s", {u'a': 1, u'b': 2}) + assert cursor._executed == b"SELECT 1, 2" + cursor.execute(b"SELECT %(a)s, %(b)s", {b'a': 3, b'b': 4}) + assert cursor._executed == b"SELECT 3, 4" From 4d278c9f43adde9b1880b0385ee3ec9f80af5ebe Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 19 Jan 2019 12:57:15 +0900 Subject: [PATCH 174/396] Update HISTORY --- HISTORY.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 3fe5212d..0583cab9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,9 +1,19 @@ +====================== + What's new in 1.4.1 +====================== + +Release: 2019-01-19 + +* Fix dict parameter support (#323, regression of 1.4.0) + ====================== What's new in 1.4.0 ====================== Release: 2019-01-18 +* Dropped Python 3.4 support. + * Removed ``threadsafe`` and ``embedded`` build options. * Remove some deprecated cursor classes and methods. From c29d1660234b79cdfc797a91b21524dda5372bee Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 19 Jan 2019 13:24:20 +0900 Subject: [PATCH 175/396] v1.4.1 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index e3685b88..1834c1c8 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.0 -version_info: (1,4,0,'final',0) +version: 1.4.1 +version_info: (1,4,1,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 1f6e18d345723a29a0f9f8ca8ea6a825809c97c3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 7 Feb 2019 18:05:40 +0900 Subject: [PATCH 176/396] Add some garbage for Django 1.11 compatibility (#327) Django touched private area of this library. Removing unused variables broke Django. While Django 2.0 fixed it, Django 1.11 doesn't fix it because it is in security-only fix mode. So add unused variables for Django 1.11 compatibility. They will be removed in next minor release. Fix #303, #306 --- MySQLdb/connections.py | 14 ++++++++++++-- MySQLdb/cursors.py | 9 +++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index c7c4c14f..37b52d81 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -166,6 +166,13 @@ class object, used to create cursors (keyword only) self.encoders = dict([ (k, v) for k, v in conv.items() if type(k) is not int ]) + # XXX THIS IS GARBAGE: While this is just a garbage and undocumented, + # Django 1.11 depends on it. And they don't fix it because + # they are in security-only fix mode. + # So keep this garbage for now. This will be removed in 1.5. + # See PyMySQL/mysqlclient-python#306 + self.encoders[bytes] = bytes + self._server_version = tuple([ numeric_part(n) for n in self.get_server_info().split('.')[:2] ]) self.encoding = 'ascii' # overridden in set_character_set() @@ -238,8 +245,11 @@ def literal(self, o): s = self.string_literal(o.encode(self.encoding)) elif isinstance(o, bytearray): s = self._bytes_literal(o) - elif not PY2 and isinstance(o, bytes): - s = self._bytes_literal(o) + elif isinstance(o, bytes): + if PY2: + s = self.string_literal(o) + else: + s = self._bytes_literal(o) elif isinstance(o, (tuple, list)): s = self._tuple_literal(o) else: diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 4460799e..5f6e4995 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -63,6 +63,14 @@ def __init__(self, connection): self.rowcount = -1 self.arraysize = 1 self._executed = None + + # XXX THIS IS GARBAGE: While this is totally garbage and private, + # Django 1.11 depends on it. And they don't fix it because + # they are in security-only fix mode. + # So keep this garbage for now. This will be removed in 1.5. + # See PyMySQL/mysqlclient-python#303 + self._last_executed = None + self.lastrowid = None self.messages = [] self._result = None @@ -305,6 +313,7 @@ def _query(self, q): self._do_get_result(db) self._post_get_result() self._executed = q + self._last_executed = q # XXX THIS IS GARBAGE: See above. return self.rowcount def _fetch_row(self, size=1): From 9339b153f0805a2fb38c5a0999fea7df4081eb44 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 7 Feb 2019 20:39:33 +0900 Subject: [PATCH 177/396] travis: run django-1.11.18 tests (#328) --- .travis.yml | 25 +++++++++++++++++++++++++ ci/test_mysql.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 ci/test_mysql.py diff --git a/.travis.yml b/.travis.yml index 6a1df409..c7dfe890 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,4 +36,29 @@ script: after_succes: - codecov +matrix: + include: + - &django_py27 + python: "2.7" + install: + - pip install -U pip + - wget https://github.com/django/django/archive/1.11.18.tar.gz + - tar xf 1.11.18.tar.gz + - pip install django-1.11.18/ + - cp ci/test_mysql.py django-1.11.18/tests/ + - pip install . + + before_script: + - mysql -e 'create user django identified by "secret"' + - mysql -e 'grant all on *.* to django' + - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql mysql + + script: + - cd django-1.11.18/tests/ + - ./runtests.py --parallel=1 --settings=test_mysql + + - &django_py3 + <<: *django_py27 + python: "3.7" + # vim: sw=2 ts=2 sts=2 diff --git a/ci/test_mysql.py b/ci/test_mysql.py new file mode 100644 index 00000000..d24f30f1 --- /dev/null +++ b/ci/test_mysql.py @@ -0,0 +1,45 @@ +# This is an example test settings file for use with the Django test suite. +# +# The 'sqlite3' backend requires only the ENGINE setting (an in- +# memory database will be used). All other backends will require a +# NAME and potentially authentication information. See the +# following section in the docs for more information: +# +# https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/ +# +# The different databases that Django supports behave differently in certain +# situations, so it is recommended to run the test suite against as many +# database backends as possible. You may want to create a separate settings +# file for each of the backends you test against. + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'django_default', + 'USER': 'django', + 'HOST': '127.0.0.1', + 'PASSWORD': 'secret', + 'TEST': { + 'CHARSET': 'utf8mb4', + 'COLLATION': 'utf8mb4_general_ci', + }, + }, + 'other': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'django_other', + 'USER': 'django', + 'HOST': '127.0.0.1', + 'PASSWORD': 'secret', + 'TEST': { + 'CHARSET': 'utf8mb4', + 'COLLATION': 'utf8mb4_general_ci', + }, + } +} + +SECRET_KEY = "django_tests_secret_key" + +# Use a fast hasher to speed up tests. +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.MD5PasswordHasher', +] From c3dcb16f8e497953102e4d3f55fc35aff1ca0d98 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Feb 2019 21:23:05 +0900 Subject: [PATCH 178/396] v1.4.2 --- HISTORY.rst | 12 ++++++++++++ metadata.cfg | 4 ++-- setup.cfg | 16 ---------------- 3 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 setup.cfg diff --git a/HISTORY.rst b/HISTORY.rst index 0583cab9..e8127ba9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,15 @@ +====================== + What's new in 1.4.2 +====================== + +Release: 2019-02-08 + +* Fix Django 1.11 compatibility. (#327) + mysqlclient 1.5 will not support Django 1.11. It is not because + mysqlclient will break backward compatibility, but Django used + unsupported APIs and Django 1.11 don't fix bugs including + compatibility issues. + ====================== What's new in 1.4.1 ====================== diff --git a/metadata.cfg b/metadata.cfg index 1834c1c8..fd7790b1 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.1 -version_info: (1,4,1,'final',0) +version: 1.4.2 +version_info: (1,4,2,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index eb5b5513..00000000 --- a/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[build_ext] -## Only uncomment/set these if the default configuration doesn't work -## Also see https://docs.python.org/distutils/configfile.html -# include-dirs = ? -# library-dirs = ? -# link-objects = ? -# rpath = ? -# libraries = ? - -[bdist_rpm] -doc_files = README MANIFEST doc/*.txt -vendor = MySQL-python SourceForge Project -packager = Andy Dustman -distribution-name = Red Stains Linux -requires = python -build-requires = python-devel mysql-devel zlib-devel openssl-devel From 474dac6b45b81f401172c0e330ae0014fa27b772 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 13 Feb 2019 20:55:46 +0900 Subject: [PATCH 179/396] post1 release --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index fd7790b1..c8112de2 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.2 -version_info: (1,4,2,'final',0) +version: 1.4.2.post1 +version_info: (1,4,2,'post',1) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 79cd82388e807c2b3e16bfe8b8e3ee5d2aef51f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 16 Feb 2019 15:06:48 +0200 Subject: [PATCH 180/396] travis: Fix after_success spelling, should actually upload to codecov now (#332) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c7dfe890..017c3bb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ script: - pip install -e . - pytest --cov ./MySQLdb -after_succes: +after_success: - codecov matrix: From 7893c5a97d485834c4416ff3af6c98dcb0629d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 17 Feb 2019 05:13:10 +0200 Subject: [PATCH 181/396] Grammar fixes (#333) --- doc/user_guide.rst | 2 +- tests/test_MySQLdb_nonstandard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 7c4302b4..405b444e 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -8,7 +8,7 @@ MySQLdb User's Guide Introduction ------------ -MySQLdb is a interface to the popular MySQL +MySQLdb is an interface to the popular MySQL database server that provides the Python database API. Installation diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index 2b33c398..ea05079f 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -83,7 +83,7 @@ def test_proto_info(self): def test_server_info(self): self.assertTrue(isinstance(self.conn.get_server_info(), str), - "Should return an str.") + "Should return a string.") def test_client_flag(self): conn = connection_factory( From f683ee7826363ca8f2044eeaabe5b081a580176f Mon Sep 17 00:00:00 2001 From: Carson Ip Date: Sun, 3 Mar 2019 11:13:38 +0800 Subject: [PATCH 182/396] Fix typo in docstring and minor formatting (#336) --- MySQLdb/_mysql.c | 2 +- MySQLdb/cursors.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 45df37c3..380e2ef6 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -732,7 +732,7 @@ _mysql_ConnectionObject_commit( } static char _mysql_ConnectionObject_rollback__doc__[] = -"Rolls backs the current transaction\n\ +"Rolls back the current transaction\n\ "; static PyObject * _mysql_ConnectionObject_rollback( diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 5f6e4995..82eb64e7 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -471,7 +471,7 @@ class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, BaseCursor): - """This is a Cursor class that returns rows as dictionaries and + """This is a Cursor class that returns rows as dictionaries and stores the result set in the client.""" From 1a67ae2cc2da1a2bf511cf70b20dcd5fca5ba7fe Mon Sep 17 00:00:00 2001 From: Carson Ip Date: Mon, 4 Mar 2019 18:24:43 +0800 Subject: [PATCH 183/396] Fix description of SEGV issue in HISTORY (#337) --- HISTORY.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e8127ba9..c5221a21 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -73,8 +73,9 @@ Release: 2018-12-04 * Add ``Connection._get_native_connection`` for XTA project (#269) -* Fix SEGV on MariaDB Connector/C when ``Connection.close()`` is called - for closed connection. (#270, #272, #276) +* Fix SEGV on MariaDB Connector/C when some methods of ``Connection`` + objects are called after ``Connection.close()`` is called. (#270, #272, #276) + See https://jira.mariadb.org/browse/CONC-289 * Fix ``Connection.client_flag`` (#266) From e52812fd15084344910d4eb55b4a6d3b12ef1baf Mon Sep 17 00:00:00 2001 From: Keery Nie Date: Tue, 19 Mar 2019 15:55:38 +0800 Subject: [PATCH 184/396] Improve docstring of `use_unicode` and `charset` options (#345) --- MySQLdb/connections.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 37b52d81..cbfc9afb 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -73,21 +73,23 @@ class object, used to create cursors (keyword only) :param bool use_unicode: If True, text-like columns are returned as unicode objects - using the connection's character set. Otherwise, text-like - columns are returned as strings. columns are returned as - normal strings. Unicode objects will always be encoded to - the connection's character set regardless of this setting. - Default to False on Python 2 and True on Python 3. + using the connection's character set. Otherwise, text-like + columns are returned as bytes. Unicode objects will always + be encoded to the connection's character set regardless of + this setting. + Default to False on Python 2 and True on Python 3 + so that you can always get python `str` object by default. :param str charset: If supplied, the connection character set will be changed - to this character set (MySQL-4.1 and newer). This implies - use_unicode=True. + to this character set. + On Python 2, this option changes default value of `use_unicode` + option from False to True. :param str sql_mode: If supplied, the session SQL mode will be changed to this - setting (MySQL-4.1 and newer). For more details and legal - values, see the MySQL documentation. + setting. + For more details and legal values, see the MySQL documentation. :param int client_flag: flags to use or 0 (see MySQL docs or constants/CLIENTS.py) From e04c5972f526805e7c310aa213d1eadbabb45371 Mon Sep 17 00:00:00 2001 From: Jason Fried Date: Tue, 19 Mar 2019 01:02:44 -0700 Subject: [PATCH 185/396] Add missing break (#346) Fixes #343 --- MySQLdb/_mysql.c | 1 + 1 file changed, 1 insertion(+) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 380e2ef6..3c846274 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1147,6 +1147,7 @@ _mysql_field_to_python( #endif // Call converter with bytes binary = 1; + break; default: // e.g. FIELD_TYPE_DATETIME, etc. // Call converter with unicode string binary = 0; From b66971ee36be96b772ae7fdec79ccc1611376f3c Mon Sep 17 00:00:00 2001 From: Jason Fried Date: Mon, 25 Mar 2019 14:46:32 -0700 Subject: [PATCH 186/396] memory leak in ConnectionObject_Initialize when mysql_real_connect fails (#350) We can't set open=0 after we mysql_init or dealloc will not cleanup the memory. Also if mysql_init returns NULL we are out of memory and shouldn't set open=1, or we could segfault in dealloc if we didn't seg before that. --- MySQLdb/_mysql.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 3c846274..f12231bc 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -444,8 +444,12 @@ _mysql_ConnectionObject_Initialize( _stringsuck(cipher, value, ssl); } - Py_BEGIN_ALLOW_THREADS ; conn = mysql_init(&(self->connection)); + if (!conn) { + PyErr_SetNone(PyExc_MemoryError); + return -1; + } + Py_BEGIN_ALLOW_THREADS ; self->open = 1; if (connect_timeout) { unsigned int timeout = connect_timeout; @@ -497,7 +501,6 @@ _mysql_ConnectionObject_Initialize( if (!conn) { _mysql_Exception(self); - self->open = 0; return -1; } From 3d046add194a2bb7c708f1d3271377ad8e78c26f Mon Sep 17 00:00:00 2001 From: Ben MacLeod Date: Thu, 6 Jun 2019 07:54:54 -0300 Subject: [PATCH 187/396] Add to the client library list for static builds (#357) --- setup_posix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup_posix.py b/setup_posix.py index 9289bb50..2b594102 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -69,7 +69,7 @@ def get_config(): # properly handle mysql client libraries that are not called libmysqlclient client = None CLIENT_LIST = ['mysqlclient', 'mysqlclient_r', 'mysqld', 'mariadb', - 'perconaserverclient', 'perconaserverclient_r'] + 'mariadbclient', 'perconaserverclient', 'perconaserverclient_r'] for c in CLIENT_LIST: if c in libraries: client = c From 6f5f48b76c4e0b81565b5b9221a6728ffb590b0a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 8 Jun 2019 23:53:46 +0900 Subject: [PATCH 188/396] use PY_SSIZE_T_CLEAN (#360) Fixes #359 --- MySQLdb/_mysql.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index f12231bc..cef196d1 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -34,6 +34,7 @@ PERFORMANCE OF THIS SOFTWARE. #define my_bool _Bool #endif +#define PY_SSIZE_T_CLEAN 1 #include "Python.h" #if PY_MAJOR_VERSION >= 3 #define IS_PY3K @@ -882,7 +883,8 @@ _mysql_escape_string( { PyObject *str; char *in, *out; - int len, size; + int len; + Py_ssize_t size; if (!PyArg_ParseTuple(args, "s#:escape_string", &in, &size)) return NULL; str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); if (!str) return PyErr_NoMemory(); @@ -1102,7 +1104,7 @@ static PyObject * _mysql_field_to_python( PyObject *converter, const char *rowitem, - unsigned long length, + Py_ssize_t length, MYSQL_FIELD *field, const char *encoding) { @@ -1157,10 +1159,10 @@ _mysql_field_to_python( } return PyObject_CallFunction(converter, binary ? "y#" : "s#", - rowitem, (int)length); + rowitem, (Py_ssize_t)length); #else return PyObject_CallFunction(converter, - "s#", rowitem, (int)length); + "s#", rowitem, (Py_ssize_t)length); #endif } @@ -1752,7 +1754,8 @@ _mysql_ConnectionObject_query( PyObject *args) { char *query; - int len, r; + Py_ssize_t len; + int r; if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; check_connection(self); @@ -1774,7 +1777,8 @@ _mysql_ConnectionObject_send_query( PyObject *args) { char *query; - int len, r; + Py_ssize_t len; + int r; MYSQL *mysql = &(self->connection); if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; check_connection(self); From 23addef86d4bd3b00975d203d05e89fafbd82e6c Mon Sep 17 00:00:00 2001 From: ckclark Date: Wed, 17 Jul 2019 13:10:32 +0800 Subject: [PATCH 189/396] Remove unused "NULL" from `__all__` in `__init__.py` (#369) Since the symbol "NULL" is removed in 819688b6, it should be also removed from `__all__`. Fix issue #368. --- MySQLdb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 92b5d264..d4c7fa56 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -89,7 +89,7 @@ def Connect(*args, **kwargs): 'Date', 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks', 'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError', 'InterfaceError', 'InternalError', - 'MySQLError', 'NULL', 'NUMBER', 'NotSupportedError', 'DBAPISet', + 'MySQLError', 'NUMBER', 'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect', 'connections', 'constants', 'converters', 'cursors', 'debug', 'escape', From b3e775a780a20caad0ba85d7de9458e1da71eb73 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 8 Aug 2019 18:55:21 +0900 Subject: [PATCH 190/396] fix Cursor.executemany created many circular references (#375) --- MySQLdb/cursors.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 82eb64e7..ee834e45 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -110,14 +110,17 @@ def ensure_bytes(x): return x if isinstance(args, (tuple, list)): - return tuple(literal(ensure_bytes(arg)) for arg in args) + ret = tuple(literal(ensure_bytes(arg)) for arg in args) elif isinstance(args, dict): - return {ensure_bytes(key): literal(ensure_bytes(val)) - for (key, val) in args.items()} + ret = {ensure_bytes(key): literal(ensure_bytes(val)) + for (key, val) in args.items()} else: # If it's not a dictionary let's try escaping it anyways. # Worst case it will throw a Value error - return literal(ensure_bytes(args)) + ret = literal(ensure_bytes(args)) + + ensure_bytes = None # break circular reference + return ret def _check_executed(self): if not self._executed: From 8e9d75925dccb21ff210528f07ee8c07ac448e80 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 8 Aug 2019 18:55:52 +0900 Subject: [PATCH 191/396] try mariadb_config when mysql_config is not found (#374) --- setup_posix.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/setup_posix.py b/setup_posix.py index 2b594102..c65c045f 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -37,6 +37,16 @@ def get_config(): if 'mysql_config' in options: _mysql_config_path = options['mysql_config'] + else: + try: + mysql_config('version') + except EnvironmentError: + # try mariadb_config + _mysql_config_path = "mariadb_config" + try: + mysql_config('version') + except EnvironmentError: + _mysql_config_path = "mysql_config" extra_objects = [] static = enabled(options, 'static') From 62c8c8c62bd0c7e943c5dc68678d9fb2a0fd1610 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 8 Aug 2019 21:31:25 +0900 Subject: [PATCH 192/396] call mysql_library_init in module initialization (#377) --- MySQLdb/_mysql.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index cef196d1..f5e98f12 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -2643,6 +2643,15 @@ init_mysql(void) { PyObject *dict, *module, *emod, *edict; + if (mysql_library_init(0, NULL, NULL)) { + PyErr_SetString(PyExc_ImportError, "_mysql: mysql_library_init failed"); +#ifdef IS_PY3K + return NULL; +#else + return; +#endif + } + #ifdef IS_PY3K if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) return NULL; From a8329a3096596d65ff9ab26cdd579b1d1ca3a0af Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 8 Aug 2019 21:48:28 +0900 Subject: [PATCH 193/396] travis: use build names (#378) --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 017c3bb9..76952ceb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: - "3.6" - "3.5" - "2.7" + - "3.8-dev" cache: pip @@ -39,6 +40,7 @@ after_success: matrix: include: - &django_py27 + name: "Django test (Python 2.7) python: "2.7" install: - pip install -U pip @@ -59,6 +61,7 @@ matrix: - &django_py3 <<: *django_py27 + name: "Django test (Python 3.7)" python: "3.7" # vim: sw=2 ts=2 sts=2 From 5a40d5772ec8e634626241cdfbe19acb35f3313e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 9 Aug 2019 17:55:33 +0900 Subject: [PATCH 194/396] 1.4.3 --- HISTORY.rst | 15 +++++++++++++++ metadata.cfg | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index c5221a21..3245dfd6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,18 @@ +====================== + What's new in 1.4.3 +====================== + +Release: 2019-08-09 + +* ``--static`` build supports ``libmariadbclient.a`` +* Try ``mariadb_config`` when ``mysql_config`` is not found +* Fixed warning happend in Python 3.8 (#359) +* Fixed ``from MySQLdb import *``, while I don't recommend it. (#369) +* Fixed SEGV ``MySQLdb.escape_string("1")`` when libmariadb is used and + no connection is created. (#367) +* Fixed many circular references are created in ``Cursor.executemany()``. (#375) + + ====================== What's new in 1.4.2 ====================== diff --git a/metadata.cfg b/metadata.cfg index c8112de2..19df5421 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.2.post1 -version_info: (1,4,2,'post',1) +version: 1.4.3 +version_info: (1,4,3,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com @@ -25,6 +25,7 @@ classifiers: Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: From b400acddd0e1ee28e3fcfecc77512f660765efc9 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 11 Aug 2019 11:23:40 +0900 Subject: [PATCH 195/396] set charset before connect (#382) --- MySQLdb/_mysql.c | 13 +++++++++---- MySQLdb/connections.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index f5e98f12..2a431dc3 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -397,7 +397,7 @@ _mysql_ConnectionObject_Initialize( "read_default_file", "read_default_group", "client_flag", "ssl", "local_infile", - "read_timeout", "write_timeout", + "read_timeout", "write_timeout", "charset", NULL } ; int connect_timeout = 0; int read_timeout = 0; @@ -405,13 +405,14 @@ _mysql_ConnectionObject_Initialize( int compress = -1, named_pipe = -1, local_infile = -1; char *init_command=NULL, *read_default_file=NULL, - *read_default_group=NULL; + *read_default_group=NULL, + *charset=NULL; self->converter = NULL; self->open = 0; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|ssssisOiiisssiOiii:connect", + "|ssssisOiiisssiOiiis:connect", kwlist, &host, &user, &passwd, &db, &port, &unix_socket, &conv, @@ -422,7 +423,8 @@ _mysql_ConnectionObject_Initialize( &client_flag, &ssl, &local_infile, &read_timeout, - &write_timeout + &write_timeout, + &charset )) return -1; @@ -486,6 +488,9 @@ _mysql_ConnectionObject_Initialize( if (ssl) { mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); } + if (charset) { + mysql_options(&(self->connection), MYSQL_SET_CHARSET_NAME, charset); + } conn = mysql_real_connect(&(self->connection), host, user, passwd, db, port, unix_socket, client_flag); diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index cbfc9afb..5c5e6d48 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -140,7 +140,7 @@ class object, used to create cursors (keyword only) kwargs2['conv'] = conv2 cursorclass = kwargs2.pop('cursorclass', self.default_cursor) - charset = kwargs2.pop('charset', '') + charset = kwargs2.get('charset', '') if charset or not PY2: use_unicode = True From 17045e8ed04579ec2d80e9daa9c7b69fbfcd5c2f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 12 Aug 2019 00:35:04 +0900 Subject: [PATCH 196/396] 1.4.4 --- HISTORY.rst | 11 +++++++++++ metadata.cfg | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 3245dfd6..7a30a0a2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,14 @@ +====================== + What's new in 1.4.4 +====================== + +Release: 2019-08-12 + +* ``charset`` option is passed to ``mysql_options(mysql, MYSQL_SET_CHARSET_NAME, charset)`` + before ``mysql_real_connect`` is called. + This avoid extra ``SET NAMES `` query when creating connection. + + ====================== What's new in 1.4.3 ====================== diff --git a/metadata.cfg b/metadata.cfg index 19df5421..96e665f7 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.3 -version_info: (1,4,3,'final',0) +version: 1.4.4 +version_info: (1,4,4,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From acf4cdcf896105cfead13f8be7bc9baa38e849d7 Mon Sep 17 00:00:00 2001 From: Mackenzie Boyd Date: Wed, 23 Oct 2019 03:19:23 -0700 Subject: [PATCH 197/396] Add 'auth_plugin' option (#389) --- MySQLdb/_mysql.c | 12 +++++++++--- MySQLdb/connections.py | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 2a431dc3..6e4269e3 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -398,6 +398,7 @@ _mysql_ConnectionObject_Initialize( "client_flag", "ssl", "local_infile", "read_timeout", "write_timeout", "charset", + "auth_plugin", NULL } ; int connect_timeout = 0; int read_timeout = 0; @@ -406,13 +407,14 @@ _mysql_ConnectionObject_Initialize( char *init_command=NULL, *read_default_file=NULL, *read_default_group=NULL, - *charset=NULL; + *charset=NULL, + *auth_plugin=NULL; self->converter = NULL; self->open = 0; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|ssssisOiiisssiOiiis:connect", + "|ssssisOiiisssiOiiiss:connect", kwlist, &host, &user, &passwd, &db, &port, &unix_socket, &conv, @@ -424,7 +426,8 @@ _mysql_ConnectionObject_Initialize( &local_infile, &read_timeout, &write_timeout, - &charset + &charset, + &auth_plugin )) return -1; @@ -491,6 +494,9 @@ _mysql_ConnectionObject_Initialize( if (charset) { mysql_options(&(self->connection), MYSQL_SET_CHARSET_NAME, charset); } + if (auth_plugin) { + mysql_options(&(self->connection), MYSQL_DEFAULT_AUTH, auth_plugin); + } conn = mysql_real_connect(&(self->connection), host, user, passwd, db, port, unix_socket, client_flag); diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 5c5e6d48..04453fad 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -86,6 +86,11 @@ class object, used to create cursors (keyword only) On Python 2, this option changes default value of `use_unicode` option from False to True. + :param str auth_plugin: + If supplied, the connection default authentication plugin will be + changed to this value. Example values: + `mysql_native_password` or `caching_sha2_password` + :param str sql_mode: If supplied, the session SQL mode will be changed to this setting. From 52d677b6c656582464efd922b0879010813fb191 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 6 Nov 2019 17:02:34 +0900 Subject: [PATCH 198/396] v1.4.5 --- HISTORY.rst | 9 +++++++++ metadata.cfg | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 7a30a0a2..df6dbe7d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,12 @@ +====================== + What's new in 1.4.5 +====================== + +Release: 2019-11-06 + +* The ``auth_plugin`` option is added. (#389) + + ====================== What's new in 1.4.4 ====================== diff --git a/metadata.cfg b/metadata.cfg index 96e665f7..36277f34 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.4 -version_info: (1,4,4,'final',0) +version: 1.4.5 +version_info: (1,4,5,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 6222ba69b0a547be7e0e93161d97ffd28af6e1a5 Mon Sep 17 00:00:00 2001 From: Bastien Vallet Date: Thu, 7 Nov 2019 16:36:40 +0100 Subject: [PATCH 199/396] [CI] Try to fix travis (#397) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 76952ceb..e633d6b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ after_success: matrix: include: - &django_py27 - name: "Django test (Python 2.7) + name: "Django test (Python 2.7)" python: "2.7" install: - pip install -U pip From 0da81656e9daa91da61cbdb0c29ab14b507f0c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kul=C3=ADk?= Date: Thu, 7 Nov 2019 16:59:46 +0100 Subject: [PATCH 200/396] fix PyMemberDef type of two attributes (#395) --- MySQLdb/_mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 6e4269e3..648c7284 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -2302,7 +2302,7 @@ static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { }, { "server_capabilities", - T_UINT, + T_ULONG, offsetof(_mysql_ConnectionObject,connection.server_capabilities), READONLY, "Capabilities of server; consult MySQLdb.constants.CLIENT" @@ -2316,7 +2316,7 @@ static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { }, { "client_flag", - T_UINT, + T_ULONG, offsetof(_mysql_ConnectionObject,connection.client_flag), READONLY, "Client flags; refer to MySQLdb.constants.CLIENT" From 9729f251a245978e1b0ac4dd24410ca4fb1992f9 Mon Sep 17 00:00:00 2001 From: Bastien Vallet Date: Fri, 8 Nov 2019 06:05:33 +0100 Subject: [PATCH 201/396] travis: Add Python 3.8 test. (#393) --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e633d6b6..c8ee49a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,13 @@ language: python # See aws s3 ls s3://travis-python-archives/binaries/ubuntu/16.04/x86_64/ python: - "nightly" - - "pypy3.5" - - "pypy2.7-5.10.0" + - "pypy3" + - "pypy" + - "3.8" - "3.7" - "3.6" - "3.5" - "2.7" - - "3.8-dev" cache: pip From 6c67620bc65728ff269dbb7550fc88b18073a74d Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 18 Nov 2019 21:16:37 +0900 Subject: [PATCH 202/396] Try to fix RTD --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index b9f58bd7..fc7c089d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -16,7 +16,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) +#sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- From edeab3040df660af7a02c4258bcbfdaf4af1d4b6 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 18 Nov 2019 21:22:39 +0900 Subject: [PATCH 203/396] Use cp1252 for latin1 charset (#398) --- MySQLdb/_mysql.c | 3 +++ MySQLdb/connections.py | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 648c7284..64647bf3 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -217,6 +217,9 @@ _get_encoding(MYSQL *mysql) if (strncmp(utf8, cs.csname, 4) == 0) { // utf8, utf8mb3, utf8mb4 return utf8; } + else if (strncmp("latin1", cs.csname, 6) == 0) { + return "cp1252"; + } else if (strncmp("koi8r", cs.csname, 5) == 0) { return "koi8_r"; } diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 04453fad..4c33ec55 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -15,6 +15,14 @@ NotSupportedError, ProgrammingError, ) +# Mapping from MySQL charset name to Python codec name +_charset_to_encoding = { + "utf8mb4": "utf8", + "utf8mb3": "utf8", + "latin1": "cp1252", + "koi8r": "koi8_r", + "koi8u": "koi8_u", +} re_numeric_part = re.compile(r"^(\d+)") @@ -289,10 +297,7 @@ def set_character_set(self, charset): set can only be changed in MySQL-4.1 and newer. If you try to change the character set from the current value in an older version, NotSupportedError will be raised.""" - if charset in ("utf8mb4", "utf8mb3"): - py_charset = "utf8" - else: - py_charset = charset + py_charset = _charset_to_encoding.get(charset, charset) if self.character_set_name() != charset: try: super(Connection, self).set_character_set(charset) From b080cc2b75451206097895a3359ba4c8242b4481 Mon Sep 17 00:00:00 2001 From: jnozsc Date: Mon, 18 Nov 2019 17:27:50 -0800 Subject: [PATCH 204/396] homebrew migrates mysql-connector-c to mysql-client (#401) --- README.md | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/README.md b/README.md index cd91383d..080d5fda 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You may need to install the Python and MySQL development headers and libraries l * `sudo apt-get install python-dev default-libmysqlclient-dev` # Debian / Ubuntu * `sudo yum install python-devel mysql-devel` # Red Hat / CentOS -* `brew install mysql-connector-c` # macOS (Homebrew) (Currently, it has bug. See below) +* `brew install mysql-client` # macOS (Homebrew) On Windows, there are binary wheels you can install without MySQLConnector/C or MSVC. @@ -25,33 +25,6 @@ On Windows, there are binary wheels you can install without MySQLConnector/C or `sudo yum install python3-devel ` # Red Hat / CentOS -#### **Note about bug of MySQL Connector/C on macOS** - -See also: https://bugs.mysql.com/bug.php?id=86971 - -Versions of MySQL Connector/C may have incorrect default configuration options that cause compilation errors when `mysqlclient-python` is installed. (As of November 2017, this is known to be true for homebrew's `mysql-connector-c` and [official package](https://dev.mysql.com/downloads/connector/c/)) - -Modification of `mysql_config` resolves these issues as follows. - -Change - -``` -# on macOS, on or about line 112: -# Create options -libs="-L$pkglibdir" -libs="$libs -l " -``` - -to - -``` -# Create options -libs="-L$pkglibdir" -libs="$libs -lmysqlclient -lssl -lcrypto" -``` - -An improper ssl configuration may also create issues; see, e.g, `brew info openssl` for details on macOS. - ### Install from PyPI `pip install mysqlclient` From 713d3625f8db73ad4b6e7208b9fe4665a113d8af Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Nov 2019 17:29:03 +0900 Subject: [PATCH 205/396] fixup docs --- Makefile | 5 +++++ MySQLdb/__init__.py | 4 ++-- doc/MySQLdb.rst | 17 +++++++++++++++++ doc/_exceptions.rst | 7 ------- doc/_mysql.rst | 7 ------- doc/modules.rst | 7 ------- doc/user_guide.rst | 2 +- 7 files changed, 25 insertions(+), 24 deletions(-) delete mode 100644 doc/_exceptions.rst delete mode 100644 doc/_mysql.rst delete mode 100644 doc/modules.rst diff --git a/Makefile b/Makefile index 491cc75e..8c8cddc8 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,11 @@ build: python3 setup.py build_ext -if +.PHONY: doc +doc: + pip install . + pip install sphinx + cd doc && make html .PHONY: clean clean: diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index d4c7fa56..cb1bd7c8 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -18,8 +18,8 @@ from . import _mysql if version_info != _mysql.version_info: - raise ImportError("this is MySQLdb version %s, but _mysql is version %r" % - (version_info, _mysql.version_info)) + raise ImportError("this is MySQLdb version %s, but _mysql is version %r\n_mysql: %r" % + (version_info, _mysql.version_info, _mysql.__file__)) threadsafety = 1 apilevel = "2.0" diff --git a/doc/MySQLdb.rst b/doc/MySQLdb.rst index ac690900..134a40b6 100644 --- a/doc/MySQLdb.rst +++ b/doc/MySQLdb.rst @@ -41,6 +41,23 @@ MySQLdb Package :undoc-members: :show-inheritance: +:mod:`_mysql` Module +-------------------- + +.. automodule:: MySQLdb._mysql + :members: + :undoc-members: + :show-inheritance: + +:mod:`_exceptions` Module +------------------------- + +.. automodule:: MySQLdb._exceptions + :members: + :undoc-members: + :show-inheritance: + + Subpackages ----------- diff --git a/doc/_exceptions.rst b/doc/_exceptions.rst deleted file mode 100644 index b509338c..00000000 --- a/doc/_exceptions.rst +++ /dev/null @@ -1,7 +0,0 @@ -_exceptions Module -======================== - -.. automodule:: MySQLdb._exceptions - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/_mysql.rst b/doc/_mysql.rst deleted file mode 100644 index cf464b4f..00000000 --- a/doc/_mysql.rst +++ /dev/null @@ -1,7 +0,0 @@ -_mysql Module -============= - -.. automodule:: MySQLdb._mysql - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/modules.rst b/doc/modules.rst deleted file mode 100644 index 998ac460..00000000 --- a/doc/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -MySQLdb -======= - -.. toctree:: - :maxdepth: 4 - - MySQLdb diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 405b444e..0d317776 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -546,7 +546,7 @@ nextset() Some examples ............. -The ``connect()`` method works nearly the same as with `_mysql`_:: +The ``connect()`` method works nearly the same as with `MySQLDB._mysql`_:: import MySQLdb db=MySQLdb.connect(passwd="moonpie",db="thangs") From 17a75eae9f97efa1f71581052ad8f497103ed515 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 21 Nov 2019 21:06:22 +0900 Subject: [PATCH 206/396] v1.4.6 --- HISTORY.rst | 8 ++++++++ metadata.cfg | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index df6dbe7d..8c5399ad 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,11 @@ +====================== + What's new in 1.4.6 +====================== + +Release: 2019-11-21 + +* The ``cp1252`` encoding is used when charset is "latin1". (#390) + ====================== What's new in 1.4.5 ====================== diff --git a/metadata.cfg b/metadata.cfg index 36277f34..0b9d55e8 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.5 -version_info: (1,4,5,'final',0) +version: 1.4.6 +version_info: (1,4,6,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 0482702d079f5e00b4a6e038ef13ee7849e70b78 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 28 Nov 2019 20:52:41 +0900 Subject: [PATCH 207/396] Update README --- README.md | 59 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 080d5fda..8b74dfcf 100644 --- a/README.md +++ b/README.md @@ -7,37 +7,60 @@ This is a fork of [MySQLdb1](https://github.com/farcepest/MySQLdb1). This project adds Python 3 support and bug fixes. I hope this fork is merged back to MySQLdb1 like distribute was merged back to setuptools. -## Install -### Prerequisites +## Support + +**Do Not use Github Issue Tracker to ask help. OSS Maintainer is not free tech support** + +When your question looks relating to Python rather than MySQL: + +* Python mailing list [python-list](https://mail.python.org/mailman/listinfo/python-list) +* Slack [pythondev.slack.com](https://pyslackers.com/web/slack) + +Or when you have question about MySQL: + +* [MySQL Community on Slack](https://lefred.be/mysql-community-on-slack/) + + +## Install -You may need to install the Python and MySQL development headers and libraries like so: +### Windows -* `sudo apt-get install python-dev default-libmysqlclient-dev` # Debian / Ubuntu -* `sudo yum install python-devel mysql-devel` # Red Hat / CentOS -* `brew install mysql-client` # macOS (Homebrew) +Building mysqlclient on Windows is very hard. +But there are some binary wheels you can install easily. -On Windows, there are binary wheels you can install without MySQLConnector/C or MSVC. +### macOS (Homebrew) -#### Note on Python 3 : if you are using python3 then you need to install python3-dev using the following command : +Install MySQL and mysqlclient: -`sudo apt-get install python3-dev` # debian / Ubuntu +``` +# Assume you are activating Python 3 venv +$ brew install mysql +$ pip install mysqlclient +``` -`sudo yum install python3-devel ` # Red Hat / CentOS +If you don't want to install MySQL server, you can use mysql-client instead: -### Install from PyPI +``` +# Assume you are activating Python 3 venv +$ brew install mysql-client +$ echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.bash_profile +$ export PATH="/usr/local/opt/mysql-client/bin:$PATH" +$ pip install mysqlclient +``` -`pip install mysqlclient` +### Linux -NOTE: Wheels for Windows may be not released with source package. You should pin version -in your `requirements.txt` to avoid trying to install newest source package. +You may need to install the Python 3 and MySQL development headers and libraries like so: +* `$ sudo apt-get install python3-dev default-libmysqlclient-dev` # Debian / Ubuntu +* `% sudo yum install python3-devel mysql-devel` # Red Hat / CentOS -### Install from source +Then you can install mysqlclient via pip now: -1. Download source by `git clone` or [zipfile](https://github.com/PyMySQL/mysqlclient-python/archive/master.zip). -2. Customize `site.cfg` -3. `python setup.py install` +``` +$ pip install mysqlclient +``` ### Documentation From 5811678deacca6599030358254f28f779165d057 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 28 Nov 2019 21:21:35 +0900 Subject: [PATCH 208/396] Drop Python 2 support from Travis and setup (#405) --- .travis.yml | 13 +++---------- metadata.cfg | 6 ++---- setup.py | 1 + setup_common.py | 12 +++--------- setup_posix.py | 5 +---- 5 files changed, 10 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8ee49a0..f838948b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,10 @@ language: python python: - "nightly" - "pypy3" - - "pypy" - "3.8" - "3.7" - "3.6" - "3.5" - - "2.7" cache: pip @@ -39,9 +37,9 @@ after_success: matrix: include: - - &django_py27 - name: "Django test (Python 2.7)" - python: "2.7" + - &django_py3 + name: "Django 1.11 test (Python 3.7)" + python: "3.7" install: - pip install -U pip - wget https://github.com/django/django/archive/1.11.18.tar.gz @@ -59,9 +57,4 @@ matrix: - cd django-1.11.18/tests/ - ./runtests.py --parallel=1 --settings=test_mysql - - &django_py3 - <<: *django_py27 - name: "Django test (Python 3.7)" - python: "3.7" - # vim: sw=2 ts=2 sts=2 diff --git a/metadata.cfg b/metadata.cfg index 0b9d55e8..cafa3496 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.4.6 -version_info: (1,4,6,'final',0) +version: 2.0.0dev1 +version_info: (2,0,0,'dev',1) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com @@ -19,8 +19,6 @@ classifiers: Operating System :: Unix Programming Language :: C Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 diff --git a/setup.py b/setup.py index d1029962..a39e0d12 100644 --- a/setup.py +++ b/setup.py @@ -19,4 +19,5 @@ ] metadata['long_description'] = readme metadata['long_description_content_type'] = "text/markdown" +metadata['python_requires'] = '>=3.5' setuptools.setup(**metadata) diff --git a/setup_common.py b/setup_common.py index 03c39bb7..2274e3a0 100644 --- a/setup_common.py +++ b/setup_common.py @@ -1,9 +1,4 @@ -try: - # Python 2.x - from ConfigParser import SafeConfigParser -except ImportError: - # Python 3.x - from configparser import ConfigParser as SafeConfigParser +from configparser import ConfigParser as SafeConfigParser def get_metadata_and_options(): config = SafeConfigParser() @@ -28,10 +23,9 @@ def enabled(options, option): raise ValueError("Unknown value %s for option %s" % (value, option)) def create_release_file(metadata): - rel = open("MySQLdb/release.py",'w') - rel.write(""" + with open("MySQLdb/release.py",'w') as rel: + rel.write(""" __author__ = "%(author)s <%(author_email)s>" version_info = %(version_info)s __version__ = "%(version)s" """ % metadata) - rel.close() diff --git a/setup_posix.py b/setup_posix.py index c65c045f..dd88fec6 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -1,8 +1,5 @@ import os, sys -try: - from ConfigParser import SafeConfigParser -except ImportError: - from configparser import ConfigParser as SafeConfigParser +from configparser import ConfigParser as SafeConfigParser # This dequote() business is required for some older versions # of mysql_config From 026e87f7b567f9c613a46a9be671c3fe2c76a3b8 Mon Sep 17 00:00:00 2001 From: Bastien Vallet Date: Thu, 28 Nov 2019 14:04:42 +0100 Subject: [PATCH 209/396] Remove py27 support from Python sources. (#394) --- INSTALL.rst | 2 +- MySQLdb/__init__.py | 9 ++------- MySQLdb/_exceptions.py | 4 +--- MySQLdb/compat.py | 14 -------------- MySQLdb/connections.py | 27 +++++++-------------------- MySQLdb/converters.py | 4 +--- MySQLdb/cursors.py | 16 +++++++--------- metadata.cfg | 1 - tests/capabilities.py | 6 ++---- tests/test_MySQLdb_capabilities.py | 1 - tests/test_MySQLdb_nonstandard.py | 4 ++-- 11 files changed, 23 insertions(+), 65 deletions(-) delete mode 100644 MySQLdb/compat.py diff --git a/INSTALL.rst b/INSTALL.rst index f5be3f4e..0b49f3e6 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -8,7 +8,7 @@ MySQLdb Installation Prerequisites ------------- -+ Python 2.7, 3.5 or higher ++ Python 3.5 or higher + setuptools diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index cb1bd7c8..fbb5e41a 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -26,7 +26,6 @@ paramstyle = "format" from ._mysql import * -from MySQLdb.compat import PY2 from MySQLdb.constants import FIELD_TYPE from MySQLdb.times import Date, Time, Timestamp, \ DateFromTicks, TimeFromTicks, TimestampFromTicks @@ -71,12 +70,8 @@ def test_DBAPISet_set_equality_membership(): def test_DBAPISet_set_inequality_membership(): assert FIELD_TYPE.DATE != STRING -if PY2: - def Binary(x): - return bytearray(x) -else: - def Binary(x): - return bytes(x) +def Binary(x): + return bytes(x) def Connect(*args, **kwargs): """Factory function for connections.Connection.""" diff --git a/MySQLdb/_exceptions.py b/MySQLdb/_exceptions.py index 0f14f3bd..9cfff57f 100644 --- a/MySQLdb/_exceptions.py +++ b/MySQLdb/_exceptions.py @@ -4,10 +4,8 @@ https://www.python.org/dev/peps/pep-0249/ """ -from .compat import StandardError - -class MySQLError(StandardError): +class MySQLError(Exception): """Exception related to operation with MySQL.""" diff --git a/MySQLdb/compat.py b/MySQLdb/compat.py deleted file mode 100644 index f8d98ac2..00000000 --- a/MySQLdb/compat.py +++ /dev/null @@ -1,14 +0,0 @@ -import sys - -if sys.version_info[0] == 2: - PY2 = True - unicode = unicode - unichr = unichr - long = long - StandardError = StandardError -else: - PY2 = False - unicode = str - unichr = chr - long = int - StandardError = Exception diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 4c33ec55..11b996d4 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -8,7 +8,6 @@ import sys from MySQLdb import cursors, _mysql -from MySQLdb.compat import unicode, PY2 from MySQLdb._exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, @@ -85,14 +84,11 @@ class object, used to create cursors (keyword only) columns are returned as bytes. Unicode objects will always be encoded to the connection's character set regardless of this setting. - Default to False on Python 2 and True on Python 3 - so that you can always get python `str` object by default. + Default to True. :param str charset: If supplied, the connection character set will be changed to this character set. - On Python 2, this option changes default value of `use_unicode` - option from False to True. :param str auth_plugin: If supplied, the connection default authentication plugin will be @@ -154,13 +150,7 @@ class object, used to create cursors (keyword only) cursorclass = kwargs2.pop('cursorclass', self.default_cursor) charset = kwargs2.get('charset', '') - - if charset or not PY2: - use_unicode = True - else: - use_unicode = False - - use_unicode = kwargs2.pop('use_unicode', use_unicode) + use_unicode = kwargs2.pop('use_unicode', True) sql_mode = kwargs2.pop('sql_mode', '') self._binary_prefix = kwargs2.pop('binary_prefix', False) @@ -209,9 +199,9 @@ def unicode_literal(u, dummy=None): self.converter[t] = _bytes_or_str # Unlike other string/blob types, JSON is always text. # MySQL may return JSON with charset==binary. - self.converter[FIELD_TYPE.JSON] = unicode + self.converter[FIELD_TYPE.JSON] = str - self.encoders[unicode] = unicode_literal + self.encoders[str] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: if autocommit is not None: @@ -256,20 +246,17 @@ def literal(self, o): Non-standard. For internal use; do not use this in your applications. """ - if isinstance(o, unicode): + if isinstance(o, str): s = self.string_literal(o.encode(self.encoding)) elif isinstance(o, bytearray): s = self._bytes_literal(o) elif isinstance(o, bytes): - if PY2: - s = self.string_literal(o) - else: - s = self._bytes_literal(o) + s = self._bytes_literal(o) elif isinstance(o, (tuple, list)): s = self._tuple_literal(o) else: s = self.escape(o, self.encoders) - if isinstance(s, unicode): + if isinstance(s, str): s = s.encode(self.encoding) assert isinstance(s, bytes) return s diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 645e3780..c460fbd4 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -35,7 +35,6 @@ from MySQLdb._mysql import string_literal, escape from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * -from MySQLdb.compat import PY2, long, unicode from MySQLdb._exceptions import ProgrammingError NoneType = type(None) @@ -85,11 +84,10 @@ def array2Str(o, d): return Thing2Literal(o.tostring(), d) # bytes or str regarding to BINARY_FLAG. -_bytes_or_str = ((FLAG.BINARY, bytes), (None, unicode)) +_bytes_or_str = ((FLAG.BINARY, bytes), (None, str)) conversions = { int: Thing2Str, - long: Thing2Str, float: Float2Str, NoneType: None2NULL, ArrayType: array2Str, diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index ee834e45..056bb542 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -3,12 +3,10 @@ This module implements Cursors of various types for MySQLdb. By default, MySQLdb uses the Cursor class. """ -from __future__ import print_function, absolute_import from functools import partial import re import sys -from .compat import unicode from ._exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, @@ -101,7 +99,7 @@ def _escape_args(self, args, conn): literal = conn.literal def ensure_bytes(x): - if isinstance(x, unicode): + if isinstance(x, str): return x.encode(encoding) elif isinstance(x, tuple): return tuple(map(ensure_bytes, x)) @@ -187,14 +185,14 @@ def execute(self, query, args=None): pass db = self._get_db() - if isinstance(query, unicode): + if isinstance(query, str): query = query.encode(db.encoding) if args is not None: if isinstance(args, dict): nargs = {} for key, item in args.items(): - if isinstance(key, unicode): + if isinstance(key, str): key = key.encode(db.encoding) nargs[key] = db.literal(item) args = nargs @@ -242,11 +240,11 @@ def executemany(self, query, args): def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding): conn = self._get_db() escape = self._escape_args - if isinstance(prefix, unicode): + if isinstance(prefix, str): prefix = prefix.encode(encoding) - if isinstance(values, unicode): + if isinstance(values, str): values = values.encode(encoding) - if isinstance(postfix, unicode): + if isinstance(postfix, str): postfix = postfix.encode(encoding) sql = bytearray(prefix) args = iter(args) @@ -294,7 +292,7 @@ def callproc(self, procname, args=()): disconnected. """ db = self._get_db() - if isinstance(procname, unicode): + if isinstance(procname, str): procname = procname.encode(db.encoding) if args: fmt = b'@_' + procname + b'_%d=%s' diff --git a/metadata.cfg b/metadata.cfg index cafa3496..bae8bd44 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -28,7 +28,6 @@ classifiers: Topic :: Database :: Database Engines/Servers py_modules: MySQLdb._exceptions - MySQLdb.compat MySQLdb.connections MySQLdb.converters MySQLdb.cursors diff --git a/tests/capabilities.py b/tests/capabilities.py index 5d913790..15db5336 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -10,8 +10,6 @@ import unittest from configdb import connection_factory -from MySQLdb.compat import unichr - class DatabaseTest(unittest.TestCase): @@ -27,8 +25,8 @@ def setUp(self): db = connection_factory(**self.connect_kwargs) self.connection = db self.cursor = db.cursor() - self.BLOBUText = u''.join([unichr(i) for i in range(16384)]) - self.BLOBBinary = self.db_module.Binary((u''.join([unichr(i) for i in range(256)] * 16)).encode('latin1')) + self.BLOBUText = u''.join([chr(i) for i in range(16384)]) + self.BLOBBinary = self.db_module.Binary((u''.join([chr(i) for i in range(256)] * 16)).encode('latin1')) leak_test = True diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 6e39d146..d5be511f 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -5,7 +5,6 @@ from contextlib import closing import unittest import MySQLdb -from MySQLdb.compat import unicode from MySQLdb import cursors from configdb import connection_factory import warnings diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index ea05079f..fa4692ef 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -90,9 +90,9 @@ def test_client_flag(self): use_unicode=True, client_flag=MySQLdb.constants.CLIENT.FOUND_ROWS) - self.assertIsInstance(conn.client_flag, (int, MySQLdb.compat.long)) + self.assertIsInstance(conn.client_flag, int) self.assertTrue(conn.client_flag & MySQLdb.constants.CLIENT.FOUND_ROWS) - with self.assertRaises(TypeError if MySQLdb.compat.PY2 else AttributeError): + with self.assertRaises(AttributeError): conn.client_flag = 0 conn.close() From 86491882e669e3bcdeb1bb7dfdd22d72b76673c7 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 14:24:28 +0900 Subject: [PATCH 210/396] Drop Python 2 support from C source (#406) --- MySQLdb/_mysql.c | 126 +++++++++++++++-------------------------------- 1 file changed, 39 insertions(+), 87 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 64647bf3..9a7e25f7 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -36,14 +36,9 @@ PERFORMANCE OF THIS SOFTWARE. #define PY_SSIZE_T_CLEAN 1 #include "Python.h" -#if PY_MAJOR_VERSION >= 3 -#define IS_PY3K -#define PyInt_Type PyLong_Type -#define PyInt_FromString PyLong_FromString -#define PyInt_FromLong(n) PyLong_FromLong(n) -#define PyInt_Check(n) PyLong_Check(n) -#define PyInt_AS_LONG(n) PyLong_AS_LONG(n) -#define PyString_FromString(s) PyUnicode_FromString(s) + +#if PY_MAJOR_VERSION == 2 +#error "Python 2 is not supported" #endif #include "bytesobject.h" @@ -200,8 +195,8 @@ _mysql_Exception(_mysql_ConnectionObject *c) e = _mysql_OperationalError; break; } - PyTuple_SET_ITEM(t, 0, PyInt_FromLong((long)merr)); - PyTuple_SET_ITEM(t, 1, PyString_FromString(mysql_error(&(c->connection)))); + PyTuple_SET_ITEM(t, 0, PyLong_FromLong((long)merr)); + PyTuple_SET_ITEM(t, 1, PyUnicode_FromString(mysql_error(&(c->connection)))); PyErr_SetObject(e, t); Py_DECREF(t); return NULL; @@ -286,7 +281,7 @@ _mysql_ResultObject_Initialize( fields = mysql_fetch_fields(result); for (i=0; iconnection)); if (ret == (my_ulonglong)-1) - return PyInt_FromLong(-1); + return PyLong_FromLong(-1); return PyLong_FromUnsignedLongLong(ret); } @@ -790,7 +779,7 @@ _mysql_ConnectionObject_next_result( err = mysql_next_result(&(self->connection)); Py_END_ALLOW_THREADS if (err > 0) return _mysql_Exception(self); - return PyInt_FromLong(err); + return PyLong_FromLong(err); } @@ -813,7 +802,7 @@ _mysql_ConnectionObject_set_server_option( err = mysql_set_server_option(&(self->connection), flags); Py_END_ALLOW_THREADS if (err) return _mysql_Exception(self); - return PyInt_FromLong(err); + return PyLong_FromLong(err); } static char _mysql_ConnectionObject_sqlstate__doc__[] = @@ -834,7 +823,7 @@ _mysql_ConnectionObject_sqlstate( PyObject *noargs) { check_connection(self); - return PyString_FromString(mysql_sqlstate(&(self->connection))); + return PyUnicode_FromString(mysql_sqlstate(&(self->connection))); } static char _mysql_ConnectionObject_warning_count__doc__[] = @@ -849,7 +838,7 @@ _mysql_ConnectionObject_warning_count( PyObject *noargs) { check_connection(self); - return PyInt_FromLong(mysql_warning_count(&(self->connection))); + return PyLong_FromLong(mysql_warning_count(&(self->connection))); } static char _mysql_ConnectionObject_errno__doc__[] = @@ -864,7 +853,7 @@ _mysql_ConnectionObject_errno( PyObject *noargs) { check_connection(self); - return PyInt_FromLong((long)mysql_errno(&(self->connection))); + return PyLong_FromLong((long)mysql_errno(&(self->connection))); } static char _mysql_ConnectionObject_error__doc__[] = @@ -879,7 +868,7 @@ _mysql_ConnectionObject_error( PyObject *noargs) { check_connection(self); - return PyString_FromString(mysql_error(&(self->connection))); + return PyUnicode_FromString(mysql_error(&(self->connection))); } static char _mysql_escape_string__doc__[] = @@ -948,14 +937,12 @@ _mysql_string_literal( } else { s = PyObject_Str(o); if (!s) return NULL; -#ifdef IS_PY3K { PyObject *t = PyUnicode_AsASCIIString(s); Py_DECREF(s); if (!t) return NULL; s = t; } -#endif } in = PyBytes_AsString(s); size = PyBytes_GET_SIZE(s); @@ -1055,7 +1042,6 @@ _mysql_ResultObject_describe( if (!(d = PyTuple_New(n))) return NULL; for (i=0; iencoding == utf8) { name = PyUnicode_DecodeUTF8(fields[i].name, fields[i].name_length, "replace"); @@ -1068,10 +1054,6 @@ _mysql_ResultObject_describe( t = Py_BuildValue("(Niiiiii)", name, -#else - t = Py_BuildValue("(siiiiii)", - fields[i].name, -#endif (long) fields[i].type, (long) fields[i].max_length, (long) fields[i].length, @@ -1105,7 +1087,7 @@ _mysql_ResultObject_field_flags( if (!(d = PyTuple_New(n))) return NULL; for (i=0; itype) { case FIELD_TYPE_TINY_BLOB: @@ -1174,10 +1155,6 @@ _mysql_field_to_python( return PyObject_CallFunction(converter, binary ? "y#" : "s#", rowitem, (Py_ssize_t)length); -#else - return PyObject_CallFunction(converter, - "s#", rowitem, (Py_ssize_t)length); -#endif } static PyObject * @@ -1444,7 +1421,7 @@ _mysql_ConnectionObject_character_set_name( const char *s; check_connection(self); s = mysql_character_set_name(&(self->connection)); - return PyString_FromString(s); + return PyUnicode_FromString(s); } static char _mysql_ConnectionObject_set_character_set__doc__[] = @@ -1502,15 +1479,15 @@ _mysql_ConnectionObject_get_character_set_info( mysql_get_character_set_info(&(self->connection), &cs); if (!(result = PyDict_New())) return NULL; if (cs.csname) - PyDict_SetItemString(result, "name", PyString_FromString(cs.csname)); + PyDict_SetItemString(result, "name", PyUnicode_FromString(cs.csname)); if (cs.name) - PyDict_SetItemString(result, "collation", PyString_FromString(cs.name)); + PyDict_SetItemString(result, "collation", PyUnicode_FromString(cs.name)); if (cs.comment) - PyDict_SetItemString(result, "comment", PyString_FromString(cs.comment)); + PyDict_SetItemString(result, "comment", PyUnicode_FromString(cs.comment)); if (cs.dir) - PyDict_SetItemString(result, "dir", PyString_FromString(cs.dir)); - PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); - PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); + PyDict_SetItemString(result, "dir", PyUnicode_FromString(cs.dir)); + PyDict_SetItemString(result, "mbminlen", PyLong_FromLong(cs.mbminlen)); + PyDict_SetItemString(result, "mbmaxlen", PyLong_FromLong(cs.mbmaxlen)); return result; } #endif @@ -1545,7 +1522,7 @@ _mysql_get_client_info( PyObject *self, PyObject *noargs) { - return PyString_FromString(mysql_get_client_info()); + return PyUnicode_FromString(mysql_get_client_info()); } static char _mysql_ConnectionObject_get_host_info__doc__[] = @@ -1559,7 +1536,7 @@ _mysql_ConnectionObject_get_host_info( PyObject *noargs) { check_connection(self); - return PyString_FromString(mysql_get_host_info(&(self->connection))); + return PyUnicode_FromString(mysql_get_host_info(&(self->connection))); } static char _mysql_ConnectionObject_get_proto_info__doc__[] = @@ -1573,7 +1550,7 @@ _mysql_ConnectionObject_get_proto_info( PyObject *noargs) { check_connection(self); - return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); + return PyLong_FromLong((long)mysql_get_proto_info(&(self->connection))); } static char _mysql_ConnectionObject_get_server_info__doc__[] = @@ -1587,7 +1564,7 @@ _mysql_ConnectionObject_get_server_info( PyObject *noargs) { check_connection(self); - return PyString_FromString(mysql_get_server_info(&(self->connection))); + return PyUnicode_FromString(mysql_get_server_info(&(self->connection))); } static char _mysql_ConnectionObject_info__doc__[] = @@ -1604,7 +1581,7 @@ _mysql_ConnectionObject_info( const char *s; check_connection(self); s = mysql_info(&(self->connection)); - if (s) return PyString_FromString(s); + if (s) return PyUnicode_FromString(s); Py_RETURN_NONE; } @@ -1674,7 +1651,7 @@ _mysql_ConnectionObject_field_count( PyObject *noargs) { check_connection(self); - return PyInt_FromLong((long)mysql_field_count(&(self->connection))); + return PyLong_FromLong((long)mysql_field_count(&(self->connection))); } static char _mysql_ConnectionObject_fileno__doc__[] = @@ -1688,7 +1665,7 @@ _mysql_ConnectionObject_fileno( PyObject *noargs) { check_connection(self); - return PyInt_FromLong(self->connection.net.fd); + return PyLong_FromLong(self->connection.net.fd); } static char _mysql_ResultObject_num_fields__doc__[] = @@ -1700,7 +1677,7 @@ _mysql_ResultObject_num_fields( PyObject *noargs) { check_result_connection(self); - return PyInt_FromLong((long)mysql_num_fields(self->result)); + return PyLong_FromLong((long)mysql_num_fields(self->result)); } static char _mysql_ResultObject_num_rows__doc__[] = @@ -1889,7 +1866,7 @@ _mysql_ConnectionObject_stat( s = mysql_stat(&(self->connection)); Py_END_ALLOW_THREADS if (!s) return _mysql_Exception(self); - return PyString_FromString(s); + return PyUnicode_FromString(s); } static char _mysql_ConnectionObject_store_result__doc__[] = @@ -1950,7 +1927,7 @@ _mysql_ConnectionObject_thread_id( Py_BEGIN_ALLOW_THREADS pid = mysql_thread_id(&(self->connection)); Py_END_ALLOW_THREADS - return PyInt_FromLong((long)pid); + return PyLong_FromLong((long)pid); } static char _mysql_ConnectionObject_use_result__doc__[] = @@ -2013,7 +1990,7 @@ _mysql_ConnectionObject_repr( self->connection.host, self); else snprintf(buf, 300, "<_mysql.connection closed at %p>", self); - return PyString_FromString(buf); + return PyUnicode_FromString(buf); } static char _mysql_ResultObject_data_seek__doc__[] = @@ -2046,7 +2023,7 @@ _mysql_ResultObject_repr( { char buf[300]; snprintf(buf, 300, "<_mysql.result object at %p>", self); - return PyString_FromString(buf); + return PyUnicode_FromString(buf); } static PyMethodDef _mysql_ConnectionObject_methods[] = { @@ -2391,13 +2368,9 @@ _mysql_ConnectionObject_getattro( PyObject *name) { const char *cname; -#ifdef IS_PY3K cname = PyUnicode_AsUTF8(name); -#else - cname = PyString_AsString(name); -#endif if (strcmp(cname, "closed") == 0) - return PyInt_FromLong((long)!(self->open)); + return PyLong_FromLong((long)!(self->open)); return PyObject_GenericGetAttr((PyObject *)self, name); } @@ -2638,7 +2611,6 @@ an argument are now methods of the result object. Deprecated functions\n\ (as of 3.23) are NOT implemented.\n\ "; -#ifdef IS_PY3K static struct PyModuleDef _mysqlmodule = { PyModuleDef_HEAD_INIT, "_mysql", /* name of module */ @@ -2650,23 +2622,14 @@ static struct PyModuleDef _mysqlmodule = { PyMODINIT_FUNC PyInit__mysql(void) -#else -DL_EXPORT(void) -init_mysql(void) -#endif { PyObject *dict, *module, *emod, *edict; if (mysql_library_init(0, NULL, NULL)) { PyErr_SetString(PyExc_ImportError, "_mysql: mysql_library_init failed"); -#ifdef IS_PY3K return NULL; -#else - return; -#endif } -#ifdef IS_PY3K if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) return NULL; if (PyType_Ready(&_mysql_ResultObject_Type) < 0) @@ -2674,15 +2637,6 @@ init_mysql(void) module = PyModule_Create(&_mysqlmodule); if (!module) return module; /* this really should never happen */ -#else - if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) - return; - if (PyType_Ready(&_mysql_ResultObject_Type) < 0) - return; - module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, - (PyObject *)NULL, PYTHON_API_VERSION); - if (!module) return; /* this really should never happen */ -#endif if (!(dict = PyModule_GetDict(module))) goto error; if (PyDict_SetItemString(dict, "version_info", @@ -2690,7 +2644,7 @@ init_mysql(void) dict, dict))) goto error; if (PyDict_SetItemString(dict, "__version__", - PyString_FromString(QUOTE(__version__)))) + PyUnicode_FromString(QUOTE(__version__)))) goto error; if (PyDict_SetItemString(dict, "connection", (PyObject *)&_mysql_ConnectionObject_Type)) @@ -2744,9 +2698,7 @@ init_mysql(void) PyErr_SetString(PyExc_ImportError, "_mysql: init failed"); module = NULL; } -#ifdef IS_PY3K return module; -#endif } /* vim: set ts=4 sts=4 sw=4 expandtab : */ From 768ae5e2531c9109db8e4932f47094b244c7e073 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 15:22:18 +0900 Subject: [PATCH 211/396] travis: Add test for Django 2.2 (#407) --- .travis.yml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index f838948b..4d3e0108 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,15 +37,17 @@ after_success: matrix: include: - - &django_py3 + - &django_1_11 name: "Django 1.11 test (Python 3.7)" - python: "3.7" + env: + - DJANGO_VERSION=1.11.26 + python: "3.5" install: - pip install -U pip - - wget https://github.com/django/django/archive/1.11.18.tar.gz - - tar xf 1.11.18.tar.gz - - pip install django-1.11.18/ - - cp ci/test_mysql.py django-1.11.18/tests/ + - wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz + - tar xf ${DJANGO_VERSION}.tar.gz + - pip install django-${DJANGO_VERSION}/ + - cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ - pip install . before_script: @@ -54,7 +56,14 @@ matrix: - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql mysql script: - - cd django-1.11.18/tests/ + - cd django-${DJANGO_VERSION}/tests/ - ./runtests.py --parallel=1 --settings=test_mysql + - &django_2_2 + <<: *django_py27 + name: "Django 2.2 test (Python 3.8)" + python: "3.8" + env: + - DJANGO_VERSION=2.2.7 + # vim: sw=2 ts=2 sts=2 From 4a48401c75303fa04421d0fa3ae8ded529103f49 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 17:03:36 +0900 Subject: [PATCH 212/396] Remove -lcrypto -lssl -lz (#409) --- setup_posix.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup_posix.py b/setup_posix.py index dd88fec6..db82b3c6 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -90,6 +90,12 @@ def get_config(): extra_objects.append(os.path.join(library_dirs[0], 'lib%s.a' % client)) if client in libraries: libraries.remove(client) + else: + # mysql_config may have "-lmysqlclient -lz -lssl -lcrypto", but zlib and + # ssl is not used by _mysql. They are needed only for static build. + for L in ('crypto', 'ssl', 'z'): + if L in libraries: + libraries.remove(L) name = "mysqlclient" metadata['name'] = name From 3fb4eb1b11e3e94581020c752981b75518c21c21 Mon Sep 17 00:00:00 2001 From: Jason Fried Date: Fri, 29 Nov 2019 00:07:05 -0800 Subject: [PATCH 213/396] Use binary type by default for converter input. (#351) Before *Most* string fields were passed to custom converter functions as "bytes", everything else was assumed to be valid encoded utf-8 and passed as str. This could cause issues where some unknown field was not valid utf-8 and a decode error is triggered where it can't be fixed or avoided by the end user. Instead we should short list those fields that we know for sure have to be str type because the existing converters expect it to be that way. So all the date conversions, and (NEW)DECIMAL since the python decimal type will not accept bytes object. Everything else stays bytes, which is the least suprising and safest thing to do. --- MySQLdb/_mysql.c | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 9a7e25f7..f396561f 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1132,25 +1132,16 @@ _mysql_field_to_python( //fprintf(stderr, "\n"); int binary; switch (field->type) { - case FIELD_TYPE_TINY_BLOB: - case FIELD_TYPE_MEDIUM_BLOB: - case FIELD_TYPE_LONG_BLOB: - case FIELD_TYPE_BLOB: - case FIELD_TYPE_VAR_STRING: - case FIELD_TYPE_STRING: - case FIELD_TYPE_GEOMETRY: - case FIELD_TYPE_BIT: -#ifdef FIELD_TYPE_JSON - case FIELD_TYPE_JSON: -#else - case 245: // JSON -#endif - // Call converter with bytes - binary = 1; + case FIELD_TYPE_DECIMAL: + case FIELD_TYPE_NEWDECIMAL: + case FIELD_TYPE_TIMESTAMP: + case FIELD_TYPE_DATETIME: + case FIELD_TYPE_TIME: + case FIELD_TYPE_DATE: + binary = 0; // pass str, because these converters expect it break; - default: // e.g. FIELD_TYPE_DATETIME, etc. - // Call converter with unicode string - binary = 0; + default: // Default to just passing bytes + binary = 1; } return PyObject_CallFunction(converter, binary ? "y#" : "s#", From 82301a301ae71f27b78d0f5525359553ee497de4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 17:32:39 +0900 Subject: [PATCH 214/396] Drop Django 1.11 support (#411) --- .travis.yml | 16 +++++++--------- MySQLdb/cursors.py | 7 ------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d3e0108..865c00f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,10 +37,10 @@ after_success: matrix: include: - - &django_1_11 - name: "Django 1.11 test (Python 3.7)" + - &django_2_2 + name: "Django 2.2 test (Python 3.5)" env: - - DJANGO_VERSION=1.11.26 + - DJANGO_VERSION=2.2.7 python: "3.5" install: - pip install -U pip @@ -59,11 +59,9 @@ matrix: - cd django-${DJANGO_VERSION}/tests/ - ./runtests.py --parallel=1 --settings=test_mysql - - &django_2_2 - <<: *django_py27 - name: "Django 2.2 test (Python 3.8)" - python: "3.8" - env: - - DJANGO_VERSION=2.2.7 + #- &django_3_0 + # <<: *django_2_2 + # name: "Django 3.0 test (Python 3.8)" + # python: "3.8" # vim: sw=2 ts=2 sts=2 diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 056bb542..c70d0f3d 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -62,13 +62,6 @@ def __init__(self, connection): self.arraysize = 1 self._executed = None - # XXX THIS IS GARBAGE: While this is totally garbage and private, - # Django 1.11 depends on it. And they don't fix it because - # they are in security-only fix mode. - # So keep this garbage for now. This will be removed in 1.5. - # See PyMySQL/mysqlclient-python#303 - self._last_executed = None - self.lastrowid = None self.messages = [] self._result = None From 00016e096147a9d5c1727bf1ea4a7d17f64ec4ef Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 17:33:06 +0900 Subject: [PATCH 215/396] Fix some flake8 errors (#410) --- MySQLdb/connections.py | 5 ++--- MySQLdb/cursors.py | 7 +------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 11b996d4..27a04770 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -5,10 +5,9 @@ override Connection.default_cursor with a non-standard Cursor class. """ import re -import sys -from MySQLdb import cursors, _mysql -from MySQLdb._exceptions import ( +from . import cursors, _mysql +from ._exceptions import ( Warning, Error, InterfaceError, DataError, DatabaseError, OperationalError, IntegrityError, InternalError, NotSupportedError, ProgrammingError, diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index c70d0f3d..9d1bda29 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -3,14 +3,9 @@ This module implements Cursors of various types for MySQLdb. By default, MySQLdb uses the Cursor class. """ -from functools import partial import re -import sys -from ._exceptions import ( - Warning, Error, InterfaceError, DataError, - DatabaseError, OperationalError, IntegrityError, InternalError, - NotSupportedError, ProgrammingError) +from ._exceptions import ProgrammingError #: Regular expression for :meth:`Cursor.executemany`. From 8fd628ef0ff57b37eeb6ac021521c1d020f62781 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 18:33:27 +0900 Subject: [PATCH 216/396] travis: Fix Django test (#412) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 865c00f9..22041b24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ after_success: matrix: include: - &django_2_2 - name: "Django 2.2 test (Python 3.5)" + name: "Django 2.2 test" env: - DJANGO_VERSION=2.2.7 python: "3.5" @@ -46,7 +46,7 @@ matrix: - pip install -U pip - wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz - tar xf ${DJANGO_VERSION}.tar.gz - - pip install django-${DJANGO_VERSION}/ + - pip install -e django-${DJANGO_VERSION}/ - cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ - pip install . From 643220972e0164aaeaf018220ff738fb0e31bfa8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 21:15:17 +0900 Subject: [PATCH 217/396] Add Context Manager Interface to Connection. (#413) Fixes #400. --- MySQLdb/connections.py | 6 ++++++ tests/test_MySQLdb_nonstandard.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 27a04770..020b4a99 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -207,6 +207,12 @@ def unicode_literal(u, dummy=None): self.autocommit(autocommit) self.messages = [] + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + def autocommit(self, on): on = bool(on) if self.get_autocommit() != on: diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index fa4692ef..c5cacbec 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -99,3 +99,8 @@ def test_client_flag(self): def test_fileno(self): self.assertGreaterEqual(self.conn.fileno(), 0) + + def test_context_manager(self): + with connection_factory() as conn: + self.assertFalse(conn.closed) + self.assertTrue(conn.closed) From eebe7e9b987b70b90ec974153bdb09bd07ab7782 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 21:37:20 +0900 Subject: [PATCH 218/396] travis: fast_finish: true --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 22041b24..df95e89d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,8 @@ script: after_success: - codecov -matrix: +jobs: + fast_finish: true include: - &django_2_2 name: "Django 2.2 test" From 3a700b29407679a91e5a24fc86a669c4386595d7 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 29 Nov 2019 21:39:53 +0900 Subject: [PATCH 219/396] Update HISTORY --- HISTORY.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 8c5399ad..25678e55 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,14 @@ +====================== + What's new in 2.0.0 +====================== + +Release: TBD + +* Dropped Python 2 support +* Dropped Django 1.11 support +* Add context manager interface to Connection which closes the connection on ``__exit__``. + + ====================== What's new in 1.4.6 ====================== From 102fe7b990657b6ea0a90df259fc2ece06eb39ec Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 1 Dec 2019 12:06:17 +0900 Subject: [PATCH 220/396] Update README --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b74dfcf..5b280490 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,13 @@ $ pip install mysqlclient ### Linux +**Note that this is a basic step. I can not support complete step for build for all +environment. If you can see some error, you should fix it by yourself, or ask for +support in some user forum. Don't file a issue on the issue tracker.** + You may need to install the Python 3 and MySQL development headers and libraries like so: -* `$ sudo apt-get install python3-dev default-libmysqlclient-dev` # Debian / Ubuntu +* `$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essentials` # Debian / Ubuntu * `% sudo yum install python3-devel mysql-devel` # Red Hat / CentOS Then you can install mysqlclient via pip now: From 1a5ae1daf87c4a8135f72d6c870d9edafabd1185 Mon Sep 17 00:00:00 2001 From: migimigi Date: Tue, 3 Dec 2019 19:58:32 +0900 Subject: [PATCH 221/396] Added to pass ssl_mode in configuration. (#347) --- MySQLdb/_mysql.c | 49 ++++++++++++++++++++++++++++++++++++--- MySQLdb/connections.py | 7 ++++++ doc/MySQLdb.constants.rst | 1 - doc/user_guide.rst | 12 ++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index f396561f..13280ace 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -34,6 +34,14 @@ PERFORMANCE OF THIS SOFTWARE. #define my_bool _Bool #endif +#if ((MYSQL_VERSION_ID >= 50555 && MYSQL_VERSION_ID <= 50599) || \ +(MYSQL_VERSION_ID >= 50636 && MYSQL_VERSION_ID <= 50699) || \ +(MYSQL_VERSION_ID >= 50711 && MYSQL_VERSION_ID <= 50799) || \ +(MYSQL_VERSION_ID >= 80000)) && \ +!defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) +#define HAVE_ENUM_MYSQL_OPT_SSL_MODE +#endif + #define PY_SSIZE_T_CLEAN 1 #include "Python.h" @@ -371,6 +379,23 @@ static int _mysql_ResultObject_clear(_mysql_ResultObject *self) return 0; } +#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE +static int +_get_ssl_mode_num(char *ssl_mode) +{ + static char *ssl_mode_list[] = { "DISABLED", "PREFERRED", + "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY" }; + unsigned int i; + for (i=0; i < sizeof(ssl_mode_list)/sizeof(ssl_mode_list[0]); i++) { + if (strcmp(ssl_mode, ssl_mode_list[i]) == 0) { + // SSL_MODE one-based + return i + 1; + } + } + return -1; +} +#endif + static int _mysql_ConnectionObject_Initialize( _mysql_ConnectionObject *self, @@ -380,6 +405,7 @@ _mysql_ConnectionObject_Initialize( MYSQL *conn = NULL; PyObject *conv = NULL; PyObject *ssl = NULL; + char *ssl_mode = NULL; const char *key = NULL, *cert = NULL, *ca = NULL, *capath = NULL, *cipher = NULL; PyObject *ssl_keepref[5] = {NULL}; @@ -393,7 +419,7 @@ _mysql_ConnectionObject_Initialize( "connect_timeout", "compress", "named_pipe", "init_command", "read_default_file", "read_default_group", - "client_flag", "ssl", + "client_flag", "ssl", "ssl_mode", "local_infile", "read_timeout", "write_timeout", "charset", "auth_plugin", @@ -412,7 +438,7 @@ _mysql_ConnectionObject_Initialize( self->open = 0; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|ssssisOiiisssiOiiiss:connect", + "|ssssisOiiisssiOsiiiss:connect", kwlist, &host, &user, &passwd, &db, &port, &unix_socket, &conv, @@ -420,7 +446,7 @@ _mysql_ConnectionObject_Initialize( &compress, &named_pipe, &init_command, &read_default_file, &read_default_group, - &client_flag, &ssl, + &client_flag, &ssl, &ssl_mode, &local_infile, &read_timeout, &write_timeout, @@ -441,6 +467,17 @@ _mysql_ConnectionObject_Initialize( _stringsuck(key, value, ssl); _stringsuck(cipher, value, ssl); } + if (ssl_mode) { +#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE + if (_get_ssl_mode_num(ssl_mode) <= 0) { + PyErr_SetString(_mysql_NotSupportedError, "Unknown ssl_mode specification"); + return -1; + } +#else + PyErr_SetString(_mysql_NotSupportedError, "MySQL client library does not support ssl_mode specification"); + return -1; +#endif + } conn = mysql_init(&(self->connection)); if (!conn) { @@ -483,6 +520,12 @@ _mysql_ConnectionObject_Initialize( if (ssl) { mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); } +#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE + if (ssl_mode) { + int ssl_mode_num = _get_ssl_mode_num(ssl_mode); + mysql_options(&(self->connection), MYSQL_OPT_SSL_MODE, &ssl_mode_num); + } +#endif if (charset) { mysql_options(&(self->connection), MYSQL_SET_CHARSET_NAME, charset); } diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 020b4a99..1d67daa7 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -102,6 +102,13 @@ class object, used to create cursors (keyword only) :param int client_flag: flags to use or 0 (see MySQL docs or constants/CLIENTS.py) + :param str ssl_mode: + specify the security settings for connection to the server; + see the MySQL documentation for more details + (mysql_option(), MYSQL_OPT_SSL_MODE). + Only one of 'DISABLED', 'PREFERRED', 'REQUIRED', + 'VERIFY_CA', 'VERIFY_IDENTITY' can be specified. + :param dict ssl: dictionary or mapping contains SSL connection parameters; see the MySQL documentation for more details diff --git a/doc/MySQLdb.constants.rst b/doc/MySQLdb.constants.rst index 5c9a5389..a803e3e5 100644 --- a/doc/MySQLdb.constants.rst +++ b/doc/MySQLdb.constants.rst @@ -48,4 +48,3 @@ constants Package :members: :undoc-members: :show-inheritance: - diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 0d317776..e52d0f79 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -357,6 +357,18 @@ connect(parameters...) *This must be a keyword parameter.* + ssl_mode + If present, specify the security settings for the + connection to the server. For more information on ssl_mode, + see the MySQL documentation. Only one of 'DISABLED', + 'PREFERRED', 'REQUIRED', 'VERIFY_CA', 'VERIFY_IDENTITY' + can be specified. + + If not present, the session ssl_mode will be unchanged, + but in version 5.7 and later, the default is PREFERRED. + + *This must be a keyword parameter.* + ssl This parameter takes a dictionary or mapping, where the keys are parameter names used by the mysql_ssl_set_ MySQL From 96ab899bf3cfc77f80bba709fefa9f506eed5af4 Mon Sep 17 00:00:00 2001 From: Mike Zheng Date: Wed, 4 Dec 2019 17:04:32 +0800 Subject: [PATCH 222/396] Fix typo (#415) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b280490..79af2573 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ support in some user forum. Don't file a issue on the issue tracker.** You may need to install the Python 3 and MySQL development headers and libraries like so: -* `$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essentials` # Debian / Ubuntu +* `$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essential` # Debian / Ubuntu * `% sudo yum install python3-devel mysql-devel` # Red Hat / CentOS Then you can install mysqlclient via pip now: From b3a5e286e272275568da95d78e32d4f77d553f11 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 8 Dec 2019 01:05:39 +1100 Subject: [PATCH 223/396] Fix typo. (#417) Closes #416 --- MySQLdb/cursors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 9d1bda29..d5ff03bf 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -35,7 +35,7 @@ class BaseCursor(object): default number of rows fetchmany() will fetch """ - #: Max stetement size which :meth:`executemany` generates. + #: Max statement size which :meth:`executemany` generates. #: #: Max size of allowed statement is max_allowed_packet - packet_header_size. #: Default value of max_allowed_packet is 1048576. From 88c187a75a9e2b11dda1bda9e8a5de86eba19386 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 9 Dec 2019 17:38:51 +0900 Subject: [PATCH 224/396] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..650b04b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,58 @@ +--- +name: Bug report +about: Report an issue of this project +title: '' +labels: '' +assignees: '' + +--- + +### Read this first + +We don't use this issue tracker to help users. If you had trouble, please ask it on some user community. +Please use this tracker only when you are sure about it is an issue of this software. + +And please provide full information from first. I don't want to ask questions like "What is your Python version?", "Do you confirm MySQL error log?". If the issue report looks incomplete, I will just close it. + +Are you ready? Please remove until here and make a good issue report!! + + +### Describe the bug + +A clear and concise description of what the bug is. + +### To Reproduce + +**Schema** + +``` +create table .... +``` + +**Code** + +```py +con = MySQLdb.connect(...) +cur = con.cursor() +cur.execute(...) +``` + +### Environment + +**MySQL Server** + +- Server OS (e.g. Windows 10, Ubuntu 20.04): +- Server Version (e.g. MariaDB 10.3.16): + +**MySQL Client** + +- OS (e.g. Windows 10, Ubuntu 20.04): + +- Python (e.g. Homebrew Python 3.7.5): + +- Connector/C (e.g. Homebrew mysql-client 8.0.18): + + +### Additional context + +Add any other context about the problem here. From 0f1ff4c9c01695cee071315008b62c86fd57e22d Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 9 Dec 2019 17:39:19 +0900 Subject: [PATCH 225/396] Delete ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 4b7cf394..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ -**IF YOU HAVE SOME TROUBLE, IT'S MAY NOT ISSUE OF THIS PROJECT. GO STACKOVERFLOW!!!** - -If you failed to build, go Stackoverflow. -If you failed to install, go Stackoverflow. -If you failed to connect to database, go Stackoverflow. - -FYI, MySQL Connector/C 6.1.10 has bug. see https://github.com/PyMySQL/mysqlclient-python/issues/169#issuecomment-299778504 - -Only when If you're sure it's PyMySQL's issue, report the complete steps to reproduce, from creating database. - -I don't have time to investigate your issue from an incomplete code snippet. - -See also: https://medium.com/@methane/why-you-must-not-ask-questions-on-github-issues-51d741d83fde From 18163d7022e30e9e15a3ae0e04b49c11c51d4ed3 Mon Sep 17 00:00:00 2001 From: jnozsc Date: Mon, 20 Jan 2020 02:47:22 -0800 Subject: [PATCH 226/396] code cleanup and reformat (#423) --- .travis.yml | 15 +- MySQLdb/__init__.py | 160 ++++-- MySQLdb/_exceptions.py | 1 + MySQLdb/connections.py | 88 ++-- MySQLdb/constants/CLIENT.py | 4 +- MySQLdb/constants/CR.py | 14 +- MySQLdb/constants/ER.py | 16 +- MySQLdb/constants/__init__.py | 2 +- MySQLdb/converters.py | 39 +- MySQLdb/cursors.py | 108 ++-- MySQLdb/times.py | 65 ++- ci/test_mysql.py | 38 +- doc/conf.py | 80 +-- setup.py | 13 +- setup_common.py | 28 +- setup_posix.py | 113 +++-- setup_windows.py | 54 +- tests/capabilities.py | 271 +++++----- tests/configdb.py | 8 +- tests/dbapi20.py | 765 +++++++++++++++-------------- tests/test_MySQLdb_capabilities.py | 129 +++-- tests/test_MySQLdb_dbapi20.py | 201 ++++---- tests/test_MySQLdb_nonstandard.py | 60 ++- tests/test_MySQLdb_times.py | 147 +++--- tests/test_cursor.py | 81 +-- 25 files changed, 1421 insertions(+), 1079 deletions(-) diff --git a/.travis.yml b/.travis.yml index df95e89d..ea1c62ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,20 @@ jobs: script: - cd django-${DJANGO_VERSION}/tests/ - ./runtests.py --parallel=1 --settings=test_mysql - + - name: flake8 + python: "3.8" + install: + - pip install -U pip + - pip install flake8 + script: + - flake8 --ignore=E203,E501,W503 --max-line-length=88 . + - name: black + python: "3.8" + install: + - pip install -U pip + - pip install black + script: + - black --check --exclude=doc/ . #- &django_3_0 # <<: *django_2_2 # name: "Django 3.0 test (Python 3.8)" diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index fbb5e41a..824acace 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -13,28 +13,57 @@ MySQLdb.converters module. """ -from MySQLdb.release import __version__, version_info, __author__ - -from . import _mysql - -if version_info != _mysql.version_info: - raise ImportError("this is MySQLdb version %s, but _mysql is version %r\n_mysql: %r" % - (version_info, _mysql.version_info, _mysql.__file__)) - -threadsafety = 1 -apilevel = "2.0" -paramstyle = "format" - -from ._mysql import * +try: + from MySQLdb.release import version_info + from . import _mysql + + assert version_info == _mysql.version_info +except Exception: + raise ImportError( + "this is MySQLdb version {}, but _mysql is version {!r}\n_mysql: {!r}".format( + version_info, _mysql.version_info, _mysql.__file__ + ) + ) + + +from ._mysql import ( + NotSupportedError, + OperationalError, + get_client_info, + ProgrammingError, + Error, + InterfaceError, + debug, + IntegrityError, + string_literal, + MySQLError, + DataError, + escape, + escape_string, + DatabaseError, + InternalError, + Warning, +) from MySQLdb.constants import FIELD_TYPE -from MySQLdb.times import Date, Time, Timestamp, \ - DateFromTicks, TimeFromTicks, TimestampFromTicks +from MySQLdb.times import ( + Date, + Time, + Timestamp, + DateFromTicks, + TimeFromTicks, + TimestampFromTicks, +) try: frozenset except NameError: from sets import ImmutableSet as frozenset +threadsafety = 1 +apilevel = "2.0" +paramstyle = "format" + + class DBAPISet(frozenset): """A special type of set for which A == x is true if A is a DBAPISet and x is a member of that set.""" @@ -45,49 +74,106 @@ def __eq__(self, other): return other in self -STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, - FIELD_TYPE.VAR_STRING]) -BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, - FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB]) -NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, - FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, - FIELD_TYPE.TINY, FIELD_TYPE.YEAR, FIELD_TYPE.NEWDECIMAL]) -DATE = DBAPISet([FIELD_TYPE.DATE]) -TIME = DBAPISet([FIELD_TYPE.TIME]) +STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING]) +BINARY = DBAPISet( + [ + FIELD_TYPE.BLOB, + FIELD_TYPE.LONG_BLOB, + FIELD_TYPE.MEDIUM_BLOB, + FIELD_TYPE.TINY_BLOB, + ] +) +NUMBER = DBAPISet( + [ + FIELD_TYPE.DECIMAL, + FIELD_TYPE.DOUBLE, + FIELD_TYPE.FLOAT, + FIELD_TYPE.INT24, + FIELD_TYPE.LONG, + FIELD_TYPE.LONGLONG, + FIELD_TYPE.TINY, + FIELD_TYPE.YEAR, + FIELD_TYPE.NEWDECIMAL, + ] +) +DATE = DBAPISet([FIELD_TYPE.DATE]) +TIME = DBAPISet([FIELD_TYPE.TIME]) TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) -DATETIME = TIMESTAMP -ROWID = DBAPISet() +DATETIME = TIMESTAMP +ROWID = DBAPISet() + def test_DBAPISet_set_equality(): assert STRING == STRING + def test_DBAPISet_set_inequality(): assert STRING != NUMBER + def test_DBAPISet_set_equality_membership(): assert FIELD_TYPE.VAR_STRING == STRING + def test_DBAPISet_set_inequality_membership(): assert FIELD_TYPE.DATE != STRING + def Binary(x): return bytes(x) + def Connect(*args, **kwargs): """Factory function for connections.Connection.""" from MySQLdb.connections import Connection + return Connection(*args, **kwargs) -connect = Connection = Connect -__all__ = [ 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', - 'Date', 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', - 'TimestampFromTicks', 'DataError', 'DatabaseError', 'Error', - 'FIELD_TYPE', 'IntegrityError', 'InterfaceError', 'InternalError', - 'MySQLError', 'NUMBER', 'NotSupportedError', 'DBAPISet', - 'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME', - 'TIMESTAMP', 'Warning', 'apilevel', 'connect', 'connections', - 'constants', 'converters', 'cursors', 'debug', 'escape', - 'escape_string', 'get_client_info', - 'paramstyle', 'string_literal', 'threadsafety', 'version_info'] +connect = Connection = Connect +__all__ = [ + "BINARY", + "Binary", + "Connect", + "Connection", + "DATE", + "Date", + "Time", + "Timestamp", + "DateFromTicks", + "TimeFromTicks", + "TimestampFromTicks", + "DataError", + "DatabaseError", + "Error", + "FIELD_TYPE", + "IntegrityError", + "InterfaceError", + "InternalError", + "MySQLError", + "NUMBER", + "NotSupportedError", + "DBAPISet", + "OperationalError", + "ProgrammingError", + "ROWID", + "STRING", + "TIME", + "TIMESTAMP", + "Warning", + "apilevel", + "connect", + "connections", + "constants", + "converters", + "cursors", + "debug", + "escape", + "escape_string", + "get_client_info", + "paramstyle", + "string_literal", + "threadsafety", + "version_info", +] diff --git a/MySQLdb/_exceptions.py b/MySQLdb/_exceptions.py index 9cfff57f..ba35deaf 100644 --- a/MySQLdb/_exceptions.py +++ b/MySQLdb/_exceptions.py @@ -5,6 +5,7 @@ https://www.python.org/dev/peps/pep-0249/ """ + class MySQLError(Exception): """Exception related to operation with MySQL.""" diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 1d67daa7..8e226ffe 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -8,9 +8,16 @@ from . import cursors, _mysql from ._exceptions import ( - Warning, Error, InterfaceError, DataError, - DatabaseError, OperationalError, IntegrityError, InternalError, - NotSupportedError, ProgrammingError, + Warning, + Error, + InterfaceError, + DataError, + DatabaseError, + OperationalError, + IntegrityError, + InternalError, + NotSupportedError, + ProgrammingError, ) # Mapping from MySQL charset name to Python codec name @@ -24,6 +31,7 @@ re_numeric_part = re.compile(r"^(\d+)") + def numeric_part(s): """Returns the leading numeric part of a string. @@ -136,13 +144,13 @@ class object, used to create cursors (keyword only) kwargs2 = kwargs.copy() - if 'database' in kwargs2: - kwargs2['db'] = kwargs2.pop('database') - if 'password' in kwargs2: - kwargs2['passwd'] = kwargs2.pop('password') + if "database" in kwargs2: + kwargs2["db"] = kwargs2.pop("database") + if "password" in kwargs2: + kwargs2["passwd"] = kwargs2.pop("password") - if 'conv' in kwargs: - conv = kwargs['conv'] + if "conv" in kwargs: + conv = kwargs["conv"] else: conv = conversions @@ -152,30 +160,31 @@ class object, used to create cursors (keyword only) conv2[k] = v[:] else: conv2[k] = v - kwargs2['conv'] = conv2 - - cursorclass = kwargs2.pop('cursorclass', self.default_cursor) - charset = kwargs2.get('charset', '') - use_unicode = kwargs2.pop('use_unicode', True) - sql_mode = kwargs2.pop('sql_mode', '') - self._binary_prefix = kwargs2.pop('binary_prefix', False) - - client_flag = kwargs.get('client_flag', 0) - client_version = tuple([ numeric_part(n) for n in _mysql.get_client_info().split('.')[:2] ]) + kwargs2["conv"] = conv2 + + cursorclass = kwargs2.pop("cursorclass", self.default_cursor) + charset = kwargs2.get("charset", "") + use_unicode = kwargs2.pop("use_unicode", True) + sql_mode = kwargs2.pop("sql_mode", "") + self._binary_prefix = kwargs2.pop("binary_prefix", False) + + client_flag = kwargs.get("client_flag", 0) + client_version = tuple( + [numeric_part(n) for n in _mysql.get_client_info().split(".")[:2]] + ) if client_version >= (4, 1): client_flag |= CLIENT.MULTI_STATEMENTS if client_version >= (5, 0): client_flag |= CLIENT.MULTI_RESULTS - kwargs2['client_flag'] = client_flag + kwargs2["client_flag"] = client_flag # PEP-249 requires autocommit to be initially off - autocommit = kwargs2.pop('autocommit', False) + autocommit = kwargs2.pop("autocommit", False) - super(Connection, self).__init__(*args, **kwargs2) + super().__init__(*args, **kwargs2) self.cursorclass = cursorclass - self.encoders = dict([ (k, v) for k, v in conv.items() - if type(k) is not int ]) + self.encoders = {k: v for k, v in conv.items() if type(k) is not int} # XXX THIS IS GARBAGE: While this is just a garbage and undocumented, # Django 1.11 depends on it. And they don't fix it because @@ -184,9 +193,11 @@ class object, used to create cursors (keyword only) # See PyMySQL/mysqlclient-python#306 self.encoders[bytes] = bytes - self._server_version = tuple([ numeric_part(n) for n in self.get_server_info().split('.')[:2] ]) + self._server_version = tuple( + [numeric_part(n) for n in self.get_server_info().split(".")[:2]] + ) - self.encoding = 'ascii' # overridden in set_character_set() + self.encoding = "ascii" # overridden in set_character_set() db = proxy(self) def unicode_literal(u, dummy=None): @@ -200,8 +211,15 @@ def unicode_literal(u, dummy=None): self.set_sql_mode(sql_mode) if use_unicode: - for t in (FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING, FIELD_TYPE.VARCHAR, FIELD_TYPE.TINY_BLOB, - FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.BLOB): + for t in ( + FIELD_TYPE.STRING, + FIELD_TYPE.VAR_STRING, + FIELD_TYPE.VARCHAR, + FIELD_TYPE.TINY_BLOB, + FIELD_TYPE.MEDIUM_BLOB, + FIELD_TYPE.LONG_BLOB, + FIELD_TYPE.BLOB, + ): self.converter[t] = _bytes_or_str # Unlike other string/blob types, JSON is always text. # MySQL may return JSON with charset==binary. @@ -244,11 +262,11 @@ def _bytes_literal(self, bs): assert isinstance(bs, (bytes, bytearray)) x = self.string_literal(bs) # x is escaped and quoted bytes if self._binary_prefix: - return b'_binary' + x + return b"_binary" + x return x def _tuple_literal(self, t): - return b"(%s)" % (b','.join(map(self.literal, t))) + return b"(%s)" % (b",".join(map(self.literal, t))) def literal(self, o): """If o is a single object, returns an SQL literal as a string. @@ -280,7 +298,7 @@ def begin(self): """ self.query(b"BEGIN") - if not hasattr(_mysql.connection, 'warning_count'): + if not hasattr(_mysql.connection, "warning_count"): def warning_count(self): """Return the number of warnings generated from the @@ -299,11 +317,11 @@ def set_character_set(self, charset): py_charset = _charset_to_encoding.get(charset, charset) if self.character_set_name() != charset: try: - super(Connection, self).set_character_set(charset) + super().set_character_set(charset) except AttributeError: if self._server_version < (4, 1): raise NotSupportedError("server is too old to set charset") - self.query('SET NAMES %s' % charset) + self.query("SET NAMES %s" % charset) self.store_result() self.encoding = py_charset @@ -320,7 +338,8 @@ def show_warnings(self): sequence of tuples of (Level, Code, Message). This is only supported in MySQL-4.1 and up. If your server is an earlier version, an empty sequence is returned.""" - if self._server_version < (4,1): return () + if self._server_version < (4, 1): + return () self.query("SHOW WARNINGS") r = self.store_result() warnings = r.fetch_row(0) @@ -337,4 +356,5 @@ def show_warnings(self): ProgrammingError = ProgrammingError NotSupportedError = NotSupportedError + # vim: colorcolumn=100 diff --git a/MySQLdb/constants/CLIENT.py b/MySQLdb/constants/CLIENT.py index 6559917b..35f578cc 100644 --- a/MySQLdb/constants/CLIENT.py +++ b/MySQLdb/constants/CLIENT.py @@ -20,10 +20,8 @@ INTERACTIVE = 1024 SSL = 2048 IGNORE_SIGPIPE = 4096 -TRANSACTIONS = 8192 # mysql_com.h was WRONG prior to 3.23.35 +TRANSACTIONS = 8192 # mysql_com.h was WRONG prior to 3.23.35 RESERVED = 16384 SECURE_CONNECTION = 32768 MULTI_STATEMENTS = 65536 MULTI_RESULTS = 131072 - - diff --git a/MySQLdb/constants/CR.py b/MySQLdb/constants/CR.py index 753408ee..9d33cf65 100644 --- a/MySQLdb/constants/CR.py +++ b/MySQLdb/constants/CR.py @@ -9,16 +9,18 @@ """ Usage: python CR.py [/path/to/mysql/errmsg.h ...] >> CR.py """ - import fileinput, re + import fileinput + import re + data = {} error_last = None for line in fileinput.input(): - line = re.sub(r'/\*.*?\*/', '', line) - m = re.match(r'^\s*#define\s+CR_([A-Z0-9_]+)\s+(\d+)(\s.*|$)', line) + line = re.sub(r"/\*.*?\*/", "", line) + m = re.match(r"^\s*#define\s+CR_([A-Z0-9_]+)\s+(\d+)(\s.*|$)", line) if m: name = m.group(1) value = int(m.group(2)) - if name == 'ERROR_LAST': + if name == "ERROR_LAST": if error_last is None or error_last < value: error_last = value continue @@ -27,9 +29,9 @@ data[value].add(name) for value, names in sorted(data.items()): for name in sorted(names): - print('%s = %s' % (name, value)) + print("{} = {}".format(name, value)) if error_last is not None: - print('ERROR_LAST = %s' % error_last) + print("ERROR_LAST = %s" % error_last) ERROR_FIRST = 2000 diff --git a/MySQLdb/constants/ER.py b/MySQLdb/constants/ER.py index 2e623b51..fcd5bf2e 100644 --- a/MySQLdb/constants/ER.py +++ b/MySQLdb/constants/ER.py @@ -8,18 +8,20 @@ """ Usage: python ER.py [/path/to/mysql/mysqld_error.h ...] >> ER.py """ - import fileinput, re + import fileinput + import re + data = {} error_last = None for line in fileinput.input(): - line = re.sub(r'/\*.*?\*/', '', line) - m = re.match(r'^\s*#define\s+((ER|WARN)_[A-Z0-9_]+)\s+(\d+)\s*', line) + line = re.sub(r"/\*.*?\*/", "", line) + m = re.match(r"^\s*#define\s+((ER|WARN)_[A-Z0-9_]+)\s+(\d+)\s*", line) if m: name = m.group(1) - if name.startswith('ER_'): + if name.startswith("ER_"): name = name[3:] value = int(m.group(3)) - if name == 'ERROR_LAST': + if name == "ERROR_LAST": if error_last is None or error_last < value: error_last = value continue @@ -28,9 +30,9 @@ data[value].add(name) for value, names in sorted(data.items()): for name in sorted(names): - print('%s = %s' % (name, value)) + print("{} = {}".format(name, value)) if error_last is not None: - print('ERROR_LAST = %s' % error_last) + print("ERROR_LAST = %s" % error_last) ERROR_FIRST = 1000 diff --git a/MySQLdb/constants/__init__.py b/MySQLdb/constants/__init__.py index 3e774cd9..0372265b 100644 --- a/MySQLdb/constants/__init__.py +++ b/MySQLdb/constants/__init__.py @@ -1 +1 @@ -__all__ = ['CR', 'FIELD_TYPE','CLIENT','ER','FLAG'] +__all__ = ["CR", "FIELD_TYPE", "CLIENT", "ER", "FLAG"] diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index c460fbd4..33f22f74 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -32,15 +32,24 @@ """ from decimal import Decimal -from MySQLdb._mysql import string_literal, escape +from MySQLdb._mysql import string_literal from MySQLdb.constants import FIELD_TYPE, FLAG -from MySQLdb.times import * +from MySQLdb.times import ( + Date, + DateTimeType, + DateTime2literal, + DateTimeDeltaType, + DateTimeDelta2literal, + DateTime_or_None, + TimeDelta_or_None, + Date_or_None, +) from MySQLdb._exceptions import ProgrammingError -NoneType = type(None) - import array +NoneType = type(None) + try: ArrayType = array.ArrayType except AttributeError: @@ -48,28 +57,33 @@ def Bool2Str(s, d): - return b'1' if s else b'0' + return b"1" if s else b"0" + def Set2Str(s, d): # Only support ascii string. Not tested. - return string_literal(','.join(s)) + return string_literal(",".join(s)) + def Thing2Str(s, d): """Convert something into a string via str().""" return str(s) + def Float2Str(o, d): s = repr(o) - if s in ('inf', 'nan'): + if s in ("inf", "nan"): raise ProgrammingError("%s can not be used with MySQL" % s) - if 'e' not in s: - s += 'e0' + if "e" not in s: + s += "e0" return s + def None2NULL(o, d): """Convert None to NULL.""" return b"NULL" + def Thing2Literal(o, d): """Convert something into a SQL string literal. If using MySQL-3.23 or newer, string_literal() is a method of the @@ -77,12 +91,15 @@ def Thing2Literal(o, d): that method when the connection is created.""" return string_literal(o) + def Decimal2Literal(o, d): - return format(o, 'f') + return format(o, "f") + def array2Str(o, d): return Thing2Literal(o.tostring(), d) + # bytes or str regarding to BINARY_FLAG. _bytes_or_str = ((FLAG.BINARY, bytes), (None, str)) @@ -97,7 +114,6 @@ def array2Str(o, d): DateTimeDeltaType: DateTimeDelta2literal, set: Set2Str, Decimal: Decimal2Literal, - FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, FIELD_TYPE.LONG: int, @@ -112,7 +128,6 @@ def array2Str(o, d): FIELD_TYPE.DATETIME: DateTime_or_None, FIELD_TYPE.TIME: TimeDelta_or_None, FIELD_TYPE.DATE: Date_or_None, - FIELD_TYPE.TINY_BLOB: bytes, FIELD_TYPE.MEDIUM_BLOB: bytes, FIELD_TYPE.LONG_BLOB: bytes, diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index d5ff03bf..1d2ee460 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -12,13 +12,18 @@ #: executemany only supports simple bulk insert. #: You can use it to load large dataset. RE_INSERT_VALUES = re.compile( - r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)" + - r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" + - r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z", - re.IGNORECASE | re.DOTALL) - - -class BaseCursor(object): + "".join( + [ + r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)", + r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))", + r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z", + ] + ), + re.IGNORECASE | re.DOTALL, +) + + +class BaseCursor: """A base for Cursor classes. Useful attributes: description @@ -39,12 +44,20 @@ class BaseCursor(object): #: #: Max size of allowed statement is max_allowed_packet - packet_header_size. #: Default value of max_allowed_packet is 1048576. - max_stmt_length = 64*1024 + max_stmt_length = 64 * 1024 from ._exceptions import ( - MySQLError, Warning, Error, InterfaceError, - DatabaseError, DataError, OperationalError, IntegrityError, - InternalError, ProgrammingError, NotSupportedError, + MySQLError, + Warning, + Error, + InterfaceError, + DatabaseError, + DataError, + OperationalError, + IntegrityError, + InternalError, + ProgrammingError, + NotSupportedError, ) connection = None @@ -98,8 +111,10 @@ def ensure_bytes(x): if isinstance(args, (tuple, list)): ret = tuple(literal(ensure_bytes(arg)) for arg in args) elif isinstance(args, dict): - ret = {ensure_bytes(key): literal(ensure_bytes(val)) - for (key, val) in args.items()} + ret = { + ensure_bytes(key): literal(ensure_bytes(val)) + for (key, val) in args.items() + } else: # If it's not a dictionary let's try escaping it anyways. # Worst case it will throw a Value error @@ -216,16 +231,23 @@ def executemany(self, query, args): if m: q_prefix = m.group(1) % () q_values = m.group(2).rstrip() - q_postfix = m.group(3) or '' - assert q_values[0] == '(' and q_values[-1] == ')' - return self._do_execute_many(q_prefix, q_values, q_postfix, args, - self.max_stmt_length, - self._get_db().encoding) + q_postfix = m.group(3) or "" + assert q_values[0] == "(" and q_values[-1] == ")" + return self._do_execute_many( + q_prefix, + q_values, + q_postfix, + args, + self.max_stmt_length, + self._get_db().encoding, + ) self.rowcount = sum(self.execute(query, arg) for arg in args) return self.rowcount - def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding): + def _do_execute_many( + self, prefix, values, postfix, args, max_stmt_length, encoding + ): conn = self._get_db() escape = self._escape_args if isinstance(prefix, str): @@ -245,7 +267,7 @@ def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encod rows += self.execute(sql + postfix) sql = bytearray(prefix) else: - sql += b',' + sql += b"," sql += v rows += self.execute(sql + postfix) self.rowcount = rows @@ -283,15 +305,17 @@ def callproc(self, procname, args=()): if isinstance(procname, str): procname = procname.encode(db.encoding) if args: - fmt = b'@_' + procname + b'_%d=%s' - q = b'SET %s' % b','.join(fmt % (index, db.literal(arg)) - for index, arg in enumerate(args)) + fmt = b"@_" + procname + b"_%d=%s" + q = b"SET %s" % b",".join( + fmt % (index, db.literal(arg)) for index, arg in enumerate(args) + ) self._query(q) self.nextset() - q = b"CALL %s(%s)" % (procname, - b','.join([b'@_%s_%d' % (procname, i) - for i in range(len(args))])) + q = b"CALL %s(%s)" % ( + procname, + b",".join([b"@_%s_%d" % (procname, i) for i in range(len(args))]), + ) self._query(q) return args @@ -325,7 +349,7 @@ def __iter__(self): NotSupportedError = NotSupportedError -class CursorStoreResultMixIn(object): +class CursorStoreResultMixIn: """This is a MixIn class which causes the entire result set to be stored on the client side, i.e. it uses mysql_store_result(). If the result set can be very large, consider adding a LIMIT clause to your @@ -353,7 +377,7 @@ def fetchmany(self, size=None): than size. If size is not defined, cursor.arraysize is used.""" self._check_executed() end = self.rownumber + (size or self.arraysize) - result = self._rows[self.rownumber:end] + result = self._rows[self.rownumber : end] self.rownumber = min(end, len(self._rows)) return result @@ -361,13 +385,13 @@ def fetchall(self): """Fetchs all available rows from the cursor.""" self._check_executed() if self.rownumber: - result = self._rows[self.rownumber:] + result = self._rows[self.rownumber :] else: result = self._rows self.rownumber = len(self._rows) return result - def scroll(self, value, mode='relative'): + def scroll(self, value, mode="relative"): """Scroll the cursor in the result set to a new position according to mode. @@ -375,9 +399,9 @@ def scroll(self, value, mode='relative'): the current position in the result set, if set to 'absolute', value states an absolute target position.""" self._check_executed() - if mode == 'relative': + if mode == "relative": r = self.rownumber + value - elif mode == 'absolute': + elif mode == "absolute": r = value else: raise ProgrammingError("unknown scroll mode %s" % repr(mode)) @@ -387,11 +411,11 @@ def scroll(self, value, mode='relative'): def __iter__(self): self._check_executed() - result = self.rownumber and self._rows[self.rownumber:] or self._rows + result = self.rownumber and self._rows[self.rownumber :] or self._rows return iter(result) -class CursorUseResultMixIn(object): +class CursorUseResultMixIn: """This is a MixIn class which causes the result set to be stored in the server and sent row-by-row to client side, i.e. it uses @@ -438,39 +462,35 @@ def next(self): __next__ = next -class CursorTupleRowsMixIn(object): +class CursorTupleRowsMixIn: """This is a MixIn class that causes all rows to be returned as tuples, which is the standard form required by DB API.""" _fetch_type = 0 -class CursorDictRowsMixIn(object): +class CursorDictRowsMixIn: """This is a MixIn class that causes all rows to be returned as dictionaries. This is a non-standard feature.""" _fetch_type = 1 -class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, - BaseCursor): +class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, BaseCursor): """This is the standard Cursor class that returns rows as tuples and stores the result set in the client.""" -class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, - BaseCursor): +class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, BaseCursor): """This is a Cursor class that returns rows as dictionaries and stores the result set in the client.""" -class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn, - BaseCursor): +class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn, BaseCursor): """This is a Cursor class that returns rows as tuples and stores the result set in the server.""" -class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, - BaseCursor): +class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, BaseCursor): """This is a Cursor class that returns rows as dictionaries and stores the result set in the server.""" diff --git a/MySQLdb/times.py b/MySQLdb/times.py index d47c8fb8..f0e9384c 100644 --- a/MySQLdb/times.py +++ b/MySQLdb/times.py @@ -16,34 +16,50 @@ DateTimeDeltaType = timedelta DateTimeType = datetime + def DateFromTicks(ticks): """Convert UNIX ticks into a date instance.""" return date(*localtime(ticks)[:3]) + def TimeFromTicks(ticks): """Convert UNIX ticks into a time instance.""" return time(*localtime(ticks)[3:6]) + def TimestampFromTicks(ticks): """Convert UNIX ticks into a datetime instance.""" return datetime(*localtime(ticks)[:6]) + format_TIME = format_DATE = str + def format_TIMEDELTA(v): seconds = int(v.seconds) % 60 minutes = int(v.seconds // 60) % 60 hours = int(v.seconds // 3600) % 24 - return '%d %d:%d:%d' % (v.days, hours, minutes, seconds) + return "%d %d:%d:%d" % (v.days, hours, minutes, seconds) + def format_TIMESTAMP(d): """ :type d: datetime.datetime """ if d.microsecond: - fmt = "{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}" + fmt = " ".join( + [ + "{0.year:04}-{0.month:02}-{0.day:02}", + "{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}", + ] + ) else: - fmt = "{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}" + fmt = " ".join( + [ + "{0.year:04}-{0.month:02}-{0.day:02}", + "{0.hour:02}:{0.minute:02}:{0.second:02}", + ] + ) return fmt.format(d) @@ -64,32 +80,32 @@ def DateTime_or_None(s): return None return datetime( - int(s[:4]), # year - int(s[5:7]), # month - int(s[8:10]), # day + int(s[:4]), # year + int(s[5:7]), # month + int(s[8:10]), # day int(s[11:13] or 0), # hour int(s[14:16] or 0), # minute int(s[17:19] or 0), # second - micros, # microsecond + micros, # microsecond ) except ValueError: return None + def TimeDelta_or_None(s): try: - h, m, s = s.split(':') - if '.' in s: - s, ms = s.split('.') - ms = ms.ljust(6, '0') + h, m, s = s.split(":") + if "." in s: + s, ms = s.split(".") + ms = ms.ljust(6, "0") else: ms = 0 - if h[0] == '-': + if h[0] == "-": negative = True else: negative = False h, m, s, ms = abs(int(h)), int(m), int(s), int(ms) - td = timedelta(hours=h, minutes=m, seconds=s, - microseconds=ms) + td = timedelta(hours=h, minutes=m, seconds=s, microseconds=ms) if negative: return -td else: @@ -98,34 +114,33 @@ def TimeDelta_or_None(s): # unpacking or int/float conversion failed return None + def Time_or_None(s): try: - h, m, s = s.split(':') - if '.' in s: - s, ms = s.split('.') - ms = ms.ljust(6, '0') + h, m, s = s.split(":") + if "." in s: + s, ms = s.split(".") + ms = ms.ljust(6, "0") else: ms = 0 h, m, s, ms = int(h), int(m), int(s), int(ms) - return time(hour=h, minute=m, second=s, - microsecond=ms) + return time(hour=h, minute=m, second=s, microsecond=ms) except ValueError: return None + def Date_or_None(s): try: - return date( - int(s[:4]), # year - int(s[5:7]), # month - int(s[8:10]), # day - ) + return date(int(s[:4]), int(s[5:7]), int(s[8:10]),) # year # month # day except ValueError: return None + def DateTime2literal(d, c): """Format a DateTime object as an ISO timestamp.""" return string_literal(format_TIMESTAMP(d)) + def DateTimeDelta2literal(d, c): """Format a DateTimeDelta object as a time.""" return string_literal(format_TIMEDELTA(d)) diff --git a/ci/test_mysql.py b/ci/test_mysql.py index d24f30f1..88a747a6 100644 --- a/ci/test_mysql.py +++ b/ci/test_mysql.py @@ -13,33 +13,27 @@ # file for each of the backends you test against. DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'django_default', - 'USER': 'django', - 'HOST': '127.0.0.1', - 'PASSWORD': 'secret', - 'TEST': { - 'CHARSET': 'utf8mb4', - 'COLLATION': 'utf8mb4_general_ci', - }, + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": "django_default", + "USER": "django", + "HOST": "127.0.0.1", + "PASSWORD": "secret", + "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, + }, + "other": { + "ENGINE": "django.db.backends.mysql", + "NAME": "django_other", + "USER": "django", + "HOST": "127.0.0.1", + "PASSWORD": "secret", + "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, }, - 'other': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'django_other', - 'USER': 'django', - 'HOST': '127.0.0.1', - 'PASSWORD': 'secret', - 'TEST': { - 'CHARSET': 'utf8mb4', - 'COLLATION': 'utf8mb4_general_ci', - }, - } } SECRET_KEY = "django_tests_secret_key" # Use a fast hasher to speed up tests. PASSWORD_HASHERS = [ - 'django.contrib.auth.hashers.MD5PasswordHasher', + "django.contrib.auth.hashers.MD5PasswordHasher", ] diff --git a/doc/conf.py b/doc/conf.py index fc7c089d..33f9781c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # MySQLdb documentation build configuration file, created by # sphinx-quickstart on Sun Oct 07 19:36:17 2012. @@ -11,46 +10,49 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +# skip flake8 and black for this file +# flake8: noqa +import sys +import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('..')) +#sys.path.insert(0, os.path.abspath("..")) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +#needs_sphinx = "1.0" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] +extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +#source_encoding = "utf-8-sig" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'MySQLdb' -copyright = u'2012, Andy Dustman' +project = "MySQLdb" +copyright = "2012, Andy Dustman" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '1.2' +version = "1.2" # The full version, including alpha/beta/rc tags. -release = '1.2.4b4' +release = "1.2.4b4" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -58,13 +60,13 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +#today = "" # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +#today_fmt = "%B %d, %Y" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None @@ -81,7 +83,7 @@ #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] @@ -91,7 +93,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -120,11 +122,11 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +#html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. @@ -158,33 +160,30 @@ # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +#html_use_opensearch = "" # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'MySQLdbdoc' +htmlhelp_basename = "MySQLdbdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'MySQLdb.tex', u'MySQLdb Documentation', - u'Andy Dustman', 'manual'), + ("index", "MySQLdb.tex", "MySQLdb Documentation", "Andy Dustman", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -212,10 +211,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'mysqldb', u'MySQLdb Documentation', - [u'Andy Dustman'], 1) -] +man_pages = [("index", "mysqldb", "MySQLdb Documentation", ["Andy Dustman"], 1)] # If true, show URL addresses after external links. #man_show_urls = False @@ -227,9 +223,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'MySQLdb', u'MySQLdb Documentation', - u'Andy Dustman', 'MySQLdb', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "MySQLdb", + "MySQLdb Documentation", + "Andy Dustman", + "MySQLdb", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. diff --git a/setup.py b/setup.py index a39e0d12..dfa661c1 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import os -import io import setuptools @@ -10,14 +9,14 @@ else: # assume windows from setup_windows import get_config -with io.open('README.md', encoding='utf-8') as f: +with open("README.md", encoding="utf-8") as f: readme = f.read() metadata, options = get_config() -metadata['ext_modules'] = [ - setuptools.Extension("MySQLdb._mysql", sources=['MySQLdb/_mysql.c'], **options) +metadata["ext_modules"] = [ + setuptools.Extension("MySQLdb._mysql", sources=["MySQLdb/_mysql.c"], **options) ] -metadata['long_description'] = readme -metadata['long_description_content_type'] = "text/markdown" -metadata['python_requires'] = '>=3.5' +metadata["long_description"] = readme +metadata["long_description_content_type"] = "text/markdown" +metadata["python_requires"] = ">=3.5" setuptools.setup(**metadata) diff --git a/setup_common.py b/setup_common.py index 2274e3a0..28c51829 100644 --- a/setup_common.py +++ b/setup_common.py @@ -1,31 +1,37 @@ from configparser import ConfigParser as SafeConfigParser + def get_metadata_and_options(): config = SafeConfigParser() - config.read(['metadata.cfg', 'site.cfg']) + config.read(["metadata.cfg", "site.cfg"]) - metadata = dict(config.items('metadata')) - options = dict(config.items('options')) + metadata = dict(config.items("metadata")) + options = dict(config.items("options")) - metadata['py_modules'] = list(filter(None, metadata['py_modules'].split('\n'))) - metadata['classifiers'] = list(filter(None, metadata['classifiers'].split('\n'))) + metadata["py_modules"] = list(filter(None, metadata["py_modules"].split("\n"))) + metadata["classifiers"] = list(filter(None, metadata["classifiers"].split("\n"))) return metadata, options + def enabled(options, option): value = options[option] s = value.lower() - if s in ('yes','true','1','y'): + if s in ("yes", "true", "1", "y"): return True - elif s in ('no', 'false', '0', 'n'): + elif s in ("no", "false", "0", "n"): return False else: - raise ValueError("Unknown value %s for option %s" % (value, option)) + raise ValueError("Unknown value {} for option {}".format(value, option)) + def create_release_file(metadata): - with open("MySQLdb/release.py",'w') as rel: - rel.write(""" + with open("MySQLdb/release.py", "w") as rel: + rel.write( + """ __author__ = "%(author)s <%(author_email)s>" version_info = %(version_info)s __version__ = "%(version)s" -""" % metadata) +""" + % metadata + ) diff --git a/setup_posix.py b/setup_posix.py index db82b3c6..5602be84 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -1,126 +1,147 @@ -import os, sys -from configparser import ConfigParser as SafeConfigParser +import os +import sys # This dequote() business is required for some older versions # of mysql_config + def dequote(s): if not s: - raise Exception("Wrong MySQL configuration: maybe https://bugs.mysql.com/bug.php?id=86971 ?") + raise Exception( + "Wrong MySQL configuration: maybe https://bugs.mysql.com/bug.php?id=86971 ?" + ) if s[0] in "\"'" and s[0] == s[-1]: s = s[1:-1] return s + _mysql_config_path = "mysql_config" + def mysql_config(what): from os import popen - f = popen("%s --%s" % (_mysql_config_path, what)) + f = popen("{} --{}".format(_mysql_config_path, what)) data = f.read().strip().split() ret = f.close() if ret: - if ret/256: + if ret / 256: data = [] - if ret/256 > 1: - raise EnvironmentError("%s not found" % (_mysql_config_path,)) + if ret / 256 > 1: + raise OSError("{} not found".format(_mysql_config_path)) return data + def get_config(): from setup_common import get_metadata_and_options, enabled, create_release_file + global _mysql_config_path metadata, options = get_metadata_and_options() - if 'mysql_config' in options: - _mysql_config_path = options['mysql_config'] + if "mysql_config" in options: + _mysql_config_path = options["mysql_config"] else: try: - mysql_config('version') - except EnvironmentError: + mysql_config("version") + except OSError: # try mariadb_config _mysql_config_path = "mariadb_config" try: - mysql_config('version') - except EnvironmentError: + mysql_config("version") + except OSError: _mysql_config_path = "mysql_config" extra_objects = [] - static = enabled(options, 'static') + static = enabled(options, "static") # allow a command-line option to override the base config file to permit # a static build to be created via requirements.txt # - if '--static' in sys.argv: + if "--static" in sys.argv: static = True - sys.argv.remove('--static') + sys.argv.remove("--static") libs = mysql_config("libs") - library_dirs = [dequote(i[2:]) for i in libs if i.startswith('-L')] - libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')] - extra_link_args = [x for x in libs if not x.startswith(('-l', '-L'))] + library_dirs = [dequote(i[2:]) for i in libs if i.startswith("-L")] + libraries = [dequote(i[2:]) for i in libs if i.startswith("-l")] + extra_link_args = [x for x in libs if not x.startswith(("-l", "-L"))] - removable_compile_args = ('-I', '-L', '-l') - extra_compile_args = [i.replace("%", "%%") for i in mysql_config("cflags") - if i[:2] not in removable_compile_args] + removable_compile_args = ("-I", "-L", "-l") + extra_compile_args = [ + i.replace("%", "%%") + for i in mysql_config("cflags") + if i[:2] not in removable_compile_args + ] # Copy the arch flags for linking as well for i in range(len(extra_compile_args)): - if extra_compile_args[i] == '-arch': - extra_link_args += ['-arch', extra_compile_args[i + 1]] + if extra_compile_args[i] == "-arch": + extra_link_args += ["-arch", extra_compile_args[i + 1]] - include_dirs = [dequote(i[2:]) - for i in mysql_config('include') if i.startswith('-I')] + include_dirs = [ + dequote(i[2:]) for i in mysql_config("include") if i.startswith("-I") + ] if static: # properly handle mysql client libraries that are not called libmysqlclient client = None - CLIENT_LIST = ['mysqlclient', 'mysqlclient_r', 'mysqld', 'mariadb', - 'mariadbclient', 'perconaserverclient', 'perconaserverclient_r'] + CLIENT_LIST = [ + "mysqlclient", + "mysqlclient_r", + "mysqld", + "mariadb", + "mariadbclient", + "perconaserverclient", + "perconaserverclient_r", + ] for c in CLIENT_LIST: if c in libraries: client = c break - if client == 'mariadb': - client = 'mariadbclient' + if client == "mariadb": + client = "mariadbclient" if client is None: raise ValueError("Couldn't identify mysql client library") - extra_objects.append(os.path.join(library_dirs[0], 'lib%s.a' % client)) + extra_objects.append(os.path.join(library_dirs[0], "lib%s.a" % client)) if client in libraries: libraries.remove(client) else: # mysql_config may have "-lmysqlclient -lz -lssl -lcrypto", but zlib and # ssl is not used by _mysql. They are needed only for static build. - for L in ('crypto', 'ssl', 'z'): + for L in ("crypto", "ssl", "z"): if L in libraries: libraries.remove(L) name = "mysqlclient" - metadata['name'] = name + metadata["name"] = name define_macros = [ - ('version_info', metadata['version_info']), - ('__version__', metadata['version']), - ] + ("version_info", metadata["version_info"]), + ("__version__", metadata["version"]), + ] create_release_file(metadata) - del metadata['version_info'] + del metadata["version_info"] ext_options = dict( - library_dirs = library_dirs, - libraries = libraries, - extra_compile_args = extra_compile_args, - extra_link_args = extra_link_args, - include_dirs = include_dirs, - extra_objects = extra_objects, - define_macros = define_macros, + library_dirs=library_dirs, + libraries=libraries, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + include_dirs=include_dirs, + extra_objects=extra_objects, + define_macros=define_macros, ) # newer versions of gcc require libstdc++ if doing a static build if static: - ext_options['language'] = 'c++' + ext_options["language"] = "c++" return metadata, ext_options + if __name__ == "__main__": - sys.stderr.write("""You shouldn't be running this directly; it is used by setup.py.""") + sys.stderr.write( + """You shouldn't be running this directly; it is used by setup.py.""" + ) diff --git a/setup_windows.py b/setup_windows.py index cb2cbab0..917eb49d 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -1,9 +1,10 @@ -import os, sys +import os +import sys from distutils.msvccompiler import get_build_version def get_config(): - from setup_common import get_metadata_and_options, enabled, create_release_file + from setup_common import get_metadata_and_options, create_release_file metadata, options = get_metadata_and_options() @@ -16,37 +17,42 @@ def get_config(): vcversion = int(get_build_version()) if client == "mariadbclient": - library_dirs = [os.path.join(connector, 'lib', 'mariadb')] - libraries = ['kernel32', 'advapi32', 'wsock32', 'shlwapi', 'Ws2_32', client ] - include_dirs = [os.path.join(connector, 'include', 'mariadb')] + library_dirs = [os.path.join(connector, "lib", "mariadb")] + libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", client] + include_dirs = [os.path.join(connector, "include", "mariadb")] else: - library_dirs = [os.path.join(connector, r'lib\vs%d' % vcversion), - os.path.join(connector, "lib")] - libraries = ['kernel32', 'advapi32', 'wsock32', client ] - include_dirs = [os.path.join(connector, r'include')] + library_dirs = [ + os.path.join(connector, r"lib\vs%d" % vcversion), + os.path.join(connector, "lib"), + ] + libraries = ["kernel32", "advapi32", "wsock32", client] + include_dirs = [os.path.join(connector, r"include")] - extra_compile_args = ['/Zl', '/D_CRT_SECURE_NO_WARNINGS' ] - extra_link_args = ['/MANIFEST'] + extra_compile_args = ["/Zl", "/D_CRT_SECURE_NO_WARNINGS"] + extra_link_args = ["/MANIFEST"] name = "mysqlclient" - metadata['name'] = name + metadata["name"] = name define_macros = [ - ('version_info', metadata['version_info']), - ('__version__', metadata['version']), - ] + ("version_info", metadata["version_info"]), + ("__version__", metadata["version"]), + ] create_release_file(metadata) - del metadata['version_info'] + del metadata["version_info"] ext_options = dict( - library_dirs = library_dirs, - libraries = libraries, - extra_compile_args = extra_compile_args, - extra_link_args = extra_link_args, - include_dirs = include_dirs, - extra_objects = extra_objects, - define_macros = define_macros, + library_dirs=library_dirs, + libraries=libraries, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + include_dirs=include_dirs, + extra_objects=extra_objects, + define_macros=define_macros, ) return metadata, ext_options + if __name__ == "__main__": - sys.stderr.write("""You shouldn't be running this directly; it is used by setup.py.""") + sys.stderr.write( + """You shouldn't be running this directly; it is used by setup.py.""" + ) diff --git a/tests/capabilities.py b/tests/capabilities.py index 15db5336..cafe1e61 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -6,7 +6,6 @@ """ from time import time -import array import unittest from configdb import connection_factory @@ -16,35 +15,42 @@ class DatabaseTest(unittest.TestCase): db_module = None connect_args = () connect_kwargs = dict() - create_table_extra = '' + create_table_extra = "" rows = 10 debug = False def setUp(self): - import gc + db = connection_factory(**self.connect_kwargs) self.connection = db self.cursor = db.cursor() - self.BLOBUText = u''.join([chr(i) for i in range(16384)]) - self.BLOBBinary = self.db_module.Binary((u''.join([chr(i) for i in range(256)] * 16)).encode('latin1')) + self.BLOBUText = "".join([chr(i) for i in range(16384)]) + self.BLOBBinary = self.db_module.Binary( + ("".join([chr(i) for i in range(256)] * 16)).encode("latin1") + ) leak_test = True def tearDown(self): if self.leak_test: import gc + del self.cursor orphans = gc.collect() - self.failIf(orphans, "%d orphaned objects found after deleting cursor" % orphans) + self.failIf( + orphans, "%d orphaned objects found after deleting cursor" % orphans + ) del self.connection orphans = gc.collect() - self.failIf(orphans, "%d orphaned objects found after deleting connection" % orphans) + self.failIf( + orphans, "%d orphaned objects found after deleting connection" % orphans + ) def table_exists(self, name): try: - self.cursor.execute('select * from %s where 1=0' % name) - except: + self.cursor.execute("select * from %s where 1=0" % name) + except Exception: return False else: return True @@ -55,7 +61,7 @@ def quote_identifier(self, ident): def new_table_name(self): i = id(self.cursor) while True: - name = self.quote_identifier('tb%08x' % i) + name = self.quote_identifier("tb%08x" % i) if not self.table_exists(name): return name i = i + 1 @@ -71,82 +77,95 @@ def create_table(self, columndefs): """ self.table = self.new_table_name() - self.cursor.execute('CREATE TABLE %s (%s) %s' % - (self.table, - ',\n'.join(columndefs), - self.create_table_extra)) + self.cursor.execute( + "CREATE TABLE %s (%s) %s" + % (self.table, ",\n".join(columndefs), self.create_table_extra) + ) def check_data_integrity(self, columndefs, generator): # insert self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] + insert_statement = "INSERT INTO %s VALUES (%s)" % ( + self.table, + ",".join(["%s"] * len(columndefs)), + ) + data = [ + [generator(i, j) for j in range(len(columndefs))] for i in range(self.rows) + ] self.cursor.executemany(insert_statement, data) self.connection.commit() # verify - self.cursor.execute('select * from %s' % self.table) - l = self.cursor.fetchall() - self.assertEqual(len(l), self.rows) + self.cursor.execute("select * from %s" % self.table) + res = self.cursor.fetchall() + self.assertEqual(len(res), self.rows) try: for i in range(self.rows): for j in range(len(columndefs)): - self.assertEqual(l[i][j], generator(i,j)) + self.assertEqual(res[i][j], generator(i, j)) finally: if not self.debug: - self.cursor.execute('drop table %s' % (self.table)) + self.cursor.execute("drop table %s" % (self.table)) def test_transactions(self): - columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') + columndefs = ("col1 INT", "col2 VARCHAR(255)") + def generator(row, col): - if col == 0: return row - else: return ('%i' % (row%10))*255 + if col == 0: + return row + else: + return ("%i" % (row % 10)) * 255 + self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] + insert_statement = "INSERT INTO %s VALUES (%s)" % ( + self.table, + ",".join(["%s"] * len(columndefs)), + ) + data = [ + [generator(i, j) for j in range(len(columndefs))] for i in range(self.rows) + ] self.cursor.executemany(insert_statement, data) # verify self.connection.commit() - self.cursor.execute('select * from %s' % self.table) - l = self.cursor.fetchall() - self.assertEqual(len(l), self.rows) + self.cursor.execute("select * from %s" % self.table) + res = self.cursor.fetchall() + self.assertEqual(len(res), self.rows) for i in range(self.rows): for j in range(len(columndefs)): - self.assertEqual(l[i][j], generator(i,j)) - delete_statement = 'delete from %s where col1=%%s' % self.table + self.assertEqual(res[i][j], generator(i, j)) + delete_statement = "delete from %s where col1=%%s" % self.table self.cursor.execute(delete_statement, (0,)) - self.cursor.execute('select col1 from %s where col1=%s' % \ - (self.table, 0)) - l = self.cursor.fetchall() - self.assertFalse(l, "DELETE didn't work") + self.cursor.execute("select col1 from %s where col1=%s" % (self.table, 0)) + res = self.cursor.fetchall() + self.assertFalse(res, "DELETE didn't work") self.connection.rollback() - self.cursor.execute('select col1 from %s where col1=%s' % \ - (self.table, 0)) - l = self.cursor.fetchall() - self.assertTrue(len(l) == 1, "ROLLBACK didn't work") - self.cursor.execute('drop table %s' % (self.table)) + self.cursor.execute("select col1 from %s where col1=%s" % (self.table, 0)) + res = self.cursor.fetchall() + self.assertTrue(len(res) == 1, "ROLLBACK didn't work") + self.cursor.execute("drop table %s" % (self.table)) def test_truncation(self): - columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') + columndefs = ("col1 INT", "col2 VARCHAR(255)") + def generator(row, col): - if col == 0: return row - else: return ('%i' % (row%10))*((255-self.rows//2)+row) + if col == 0: + return row + else: + return ("%i" % (row % 10)) * ((255 - self.rows // 2) + row) + self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) + insert_statement = "INSERT INTO %s VALUES (%s)" % ( + self.table, + ",".join(["%s"] * len(columndefs)), + ) try: - self.cursor.execute(insert_statement, (0, '0'*256)) + self.cursor.execute(insert_statement, (0, "0" * 256)) except self.connection.DataError: pass else: - self.fail("Over-long column did not generate warnings/exception with single insert") + self.fail( + "Over-long column did not generate warnings/exception with single insert" # noqa: E501 + ) self.connection.rollback() @@ -154,143 +173,145 @@ def generator(row, col): for i in range(self.rows): data = [] for j in range(len(columndefs)): - data.append(generator(i,j)) - self.cursor.execute(insert_statement,tuple(data)) + data.append(generator(i, j)) + self.cursor.execute(insert_statement, tuple(data)) except self.connection.DataError: pass else: - self.fail("Over-long columns did not generate warnings/exception with execute()") + self.fail( + "Over-long columns did not generate warnings/exception with execute()" # noqa: E501 + ) self.connection.rollback() try: - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] + data = [ + [generator(i, j) for j in range(len(columndefs))] + for i in range(self.rows) + ] self.cursor.executemany(insert_statement, data) except self.connection.DataError: pass else: - self.fail("Over-long columns did not generate warnings/exception with executemany()") + self.fail( + "Over-long columns did not generate warnings/exception with executemany()" # noqa: E501 + ) self.connection.rollback() - self.cursor.execute('drop table %s' % (self.table)) + self.cursor.execute("drop table %s" % (self.table)) def test_CHAR(self): # Character data - def generator(row,col): - return ('%i' % ((row+col) % 10)) * 255 - self.check_data_integrity( - ('col1 char(255)','col2 char(255)'), - generator) + def generator(row, col): + return ("%i" % ((row + col) % 10)) * 255 + + self.check_data_integrity(("col1 char(255)", "col2 char(255)"), generator) def test_INT(self): # Number data - def generator(row,col): - return row*row - self.check_data_integrity( - ('col1 INT',), - generator) + def generator(row, col): + return row * row + + self.check_data_integrity(("col1 INT",), generator) def test_DECIMAL(self): # DECIMAL from decimal import Decimal - def generator(row,col): + + def generator(row, col): return Decimal("%d.%02d" % (row, col)) - self.check_data_integrity( - ('col1 DECIMAL(5,2)',), - generator) - val = Decimal('1.11111111111111119E-7') - self.cursor.execute('SELECT %s', (val,)) + self.check_data_integrity(("col1 DECIMAL(5,2)",), generator) + + val = Decimal("1.11111111111111119E-7") + self.cursor.execute("SELECT %s", (val,)) result = self.cursor.fetchone()[0] self.assertEqual(result, val) self.assertIsInstance(result, Decimal) - self.cursor.execute('SELECT %s + %s', (Decimal('0.1'), Decimal('0.2'))) + self.cursor.execute("SELECT %s + %s", (Decimal("0.1"), Decimal("0.2"))) result = self.cursor.fetchone()[0] - self.assertEqual(result, Decimal('0.3')) + self.assertEqual(result, Decimal("0.3")) self.assertIsInstance(result, Decimal) def test_DATE(self): ticks = time() - def generator(row,col): - return self.db_module.DateFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 DATE',), - generator) + + def generator(row, col): + return self.db_module.DateFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 DATE",), generator) def test_TIME(self): ticks = time() - def generator(row,col): - return self.db_module.TimeFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 TIME',), - generator) + + def generator(row, col): + return self.db_module.TimeFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 TIME",), generator) def test_DATETIME(self): ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 DATETIME',), - generator) + + def generator(row, col): + return self.db_module.TimestampFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 DATETIME",), generator) def test_TIMESTAMP(self): ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 TIMESTAMP',), - generator) + + def generator(row, col): + return self.db_module.TimestampFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 TIMESTAMP",), generator) def test_fractional_TIMESTAMP(self): ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313+row*0.7*col/3.0) - self.check_data_integrity( - ('col1 TIMESTAMP',), - generator) + + def generator(row, col): + return self.db_module.TimestampFromTicks( + ticks + row * 86400 - col * 1313 + row * 0.7 * col / 3.0 + ) + + self.check_data_integrity(("col1 TIMESTAMP",), generator) def test_LONG(self): - def generator(row,col): + def generator(row, col): if col == 0: return row else: - return self.BLOBUText # 'BLOB Text ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 LONG'), - generator) + return self.BLOBUText # 'BLOB Text ' * 1024 + + self.check_data_integrity(("col1 INT", "col2 LONG"), generator) def test_TEXT(self): - def generator(row,col): - return self.BLOBUText # 'BLOB Text ' * 1024 - self.check_data_integrity( - ('col2 TEXT',), - generator) + def generator(row, col): + return self.BLOBUText # 'BLOB Text ' * 1024 + + self.check_data_integrity(("col2 TEXT",), generator) def test_LONG_BYTE(self): - def generator(row,col): + def generator(row, col): if col == 0: return row else: - return self.BLOBBinary # 'BLOB\000Binary ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 LONG BYTE'), - generator) + return self.BLOBBinary # 'BLOB\000Binary ' * 1024 + + self.check_data_integrity(("col1 INT", "col2 LONG BYTE"), generator) def test_BLOB(self): - def generator(row,col): + def generator(row, col): if col == 0: return row else: - return self.BLOBBinary # 'BLOB\000Binary ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 BLOB'), - generator) + return self.BLOBBinary # 'BLOB\000Binary ' * 1024 + + self.check_data_integrity(("col1 INT", "col2 BLOB"), generator) def test_DOUBLE(self): for val in (18014398509481982.0, 0.1): - self.cursor.execute('SELECT %s', (val,)); + self.cursor.execute("SELECT %s", (val,)) result = self.cursor.fetchone()[0] self.assertEqual(result, val) self.assertIsInstance(result, float) diff --git a/tests/configdb.py b/tests/configdb.py index 307cc3f4..f3a56e24 100644 --- a/tests/configdb.py +++ b/tests/configdb.py @@ -3,12 +3,9 @@ from os import environ, path tests_path = path.dirname(__file__) -conf_file = environ.get('TESTDB', 'default.cnf') +conf_file = environ.get("TESTDB", "default.cnf") conf_path = path.join(tests_path, conf_file) -connect_kwargs = dict( - read_default_file = conf_path, - read_default_group = "MySQLdb-tests", -) +connect_kwargs = dict(read_default_file=conf_path, read_default_group="MySQLdb-tests",) def connection_kwargs(kwargs): @@ -19,6 +16,7 @@ def connection_kwargs(kwargs): def connection_factory(**kwargs): import MySQLdb + db_kwargs = connection_kwargs(kwargs) db = MySQLdb.connect(**db_kwargs) return db diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 79c188a5..0ca8bce6 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -''' Python DB API 2.0 driver compliance unit test suite. +""" Python DB API 2.0 driver compliance unit test suite. This software is Public Domain and may be used without restrictions. @@ -9,11 +9,11 @@ this is turning out to be a thoroughly unwholesome unit test." -- Ian Bicking -''' +""" -__rcs_id__ = '$Id$' -__version__ = '$Revision$'[11:-2] -__author__ = 'Stuart Bishop ' +__rcs_id__ = "$Id$" +__version__ = "$Revision$"[11:-2] +__author__ = "Stuart Bishop " import unittest import time @@ -64,8 +64,9 @@ # - Fix bugs in test_setoutputsize_basic and test_setinputsizes # + class DatabaseAPI20Test(unittest.TestCase): - ''' Test a database self.driver for DB API 2.0 compatibility. + """ Test a database self.driver for DB API 2.0 compatibility. This implementation tests Gadfly, but the TestCase is structured so that other self.drivers can subclass this test case to ensure compiliance with the DB-API. It is @@ -84,45 +85,45 @@ class mytest(dbapi20.DatabaseAPI20Test): Don't 'import DatabaseAPI20Test from dbapi20', or you will confuse the unit tester - just 'import dbapi20'. - ''' + """ # The self.driver module. This should be the module where the 'connect' # method is to be found driver = None - connect_args = () # List of arguments to pass to connect - connect_kw_args = {} # Keyword arguments for connect - table_prefix = 'dbapi20test_' # If you need to specify a prefix for tables + connect_args = () # List of arguments to pass to connect + connect_kw_args = {} # Keyword arguments for connect + table_prefix = "dbapi20test_" # If you need to specify a prefix for tables - ddl1 = 'create table %sbooze (name varchar(20))' % table_prefix - ddl2 = 'create table %sbarflys (name varchar(20))' % table_prefix - xddl1 = 'drop table %sbooze' % table_prefix - xddl2 = 'drop table %sbarflys' % table_prefix + ddl1 = "create table %sbooze (name varchar(20))" % table_prefix + ddl2 = "create table %sbarflys (name varchar(20))" % table_prefix + xddl1 = "drop table %sbooze" % table_prefix + xddl2 = "drop table %sbarflys" % table_prefix - lowerfunc = 'lower' # Name of stored procedure to convert string->lowercase + lowerfunc = "lower" # Name of stored procedure to convert string->lowercase # Some drivers may need to override these helpers, for example adding # a 'commit' after the execute. - def executeDDL1(self,cursor): + def executeDDL1(self, cursor): cursor.execute(self.ddl1) - def executeDDL2(self,cursor): + def executeDDL2(self, cursor): cursor.execute(self.ddl2) def setUp(self): - ''' self.drivers should override this method to perform required setup + """ self.drivers should override this method to perform required setup if any is necessary, such as creating the database. - ''' + """ pass def tearDown(self): - ''' self.drivers should override this method to perform required cleanup + """ self.drivers should override this method to perform required cleanup if any is necessary, such as deleting the test database. The default drops the tables that may be created. - ''' + """ con = self._connect() try: cur = con.cursor() - for ddl in (self.xddl1,self.xddl2): + for ddl in (self.xddl1, self.xddl2): try: cur.execute(ddl) con.commit() @@ -135,9 +136,7 @@ def tearDown(self): def _connect(self): try: - return self.driver.connect( - *self.connect_args,**self.connect_kw_args - ) + return self.driver.connect(*self.connect_args, **self.connect_kw_args) except AttributeError: self.fail("No connect method found in self.driver module") @@ -150,7 +149,7 @@ def test_apilevel(self): # Must exist apilevel = self.driver.apilevel # Must equal 2.0 - self.assertEqual(apilevel,'2.0') + self.assertEqual(apilevel, "2.0") except AttributeError: self.fail("Driver doesn't define apilevel") @@ -159,7 +158,7 @@ def test_threadsafety(self): # Must exist threadsafety = self.driver.threadsafety # Must be a valid value - self.assertTrue(threadsafety in (0,1,2,3)) + self.assertTrue(threadsafety in (0, 1, 2, 3)) except AttributeError: self.fail("Driver doesn't define threadsafety") @@ -168,38 +167,24 @@ def test_paramstyle(self): # Must exist paramstyle = self.driver.paramstyle # Must be a valid value - self.assertTrue(paramstyle in ( - 'qmark','numeric','named','format','pyformat' - )) + self.assertTrue( + paramstyle in ("qmark", "numeric", "named", "format", "pyformat") + ) except AttributeError: self.fail("Driver doesn't define paramstyle") def test_Exceptions(self): # Make sure required exceptions exist, and are in the # defined hierarchy. - self.assertTrue(issubclass(self.driver.Warning,Exception)) - self.assertTrue(issubclass(self.driver.Error,Exception)) - self.assertTrue( - issubclass(self.driver.InterfaceError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.DatabaseError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.OperationalError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.IntegrityError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.InternalError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.ProgrammingError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.NotSupportedError,self.driver.Error) - ) + self.assertTrue(issubclass(self.driver.Warning, Exception)) + self.assertTrue(issubclass(self.driver.Error, Exception)) + self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.DatabaseError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.OperationalError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.IntegrityError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.InternalError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.ProgrammingError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.NotSupportedError, self.driver.Error)) def test_ExceptionsAsConnectionAttributes(self): # OPTIONAL EXTENSION @@ -220,7 +205,6 @@ def test_ExceptionsAsConnectionAttributes(self): self.assertTrue(con.ProgrammingError is drv.ProgrammingError) self.assertTrue(con.NotSupportedError is drv.NotSupportedError) - def test_commit(self): con = self._connect() try: @@ -233,7 +217,7 @@ def test_rollback(self): con = self._connect() # If rollback is defined, it should either work or throw # the documented exception - if hasattr(con,'rollback'): + if hasattr(con, "rollback"): try: con.rollback() except self.driver.NotSupportedError: @@ -242,7 +226,7 @@ def test_rollback(self): def test_cursor(self): con = self._connect() try: - cur = con.cursor() + _ = con.cursor() finally: con.close() @@ -254,14 +238,14 @@ def test_cursor_isolation(self): cur1 = con.cursor() cur2 = con.cursor() self.executeDDL1(cur1) - cur1.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) + cur1.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) cur2.execute("select name from %sbooze" % self.table_prefix) booze = cur2.fetchall() - self.assertEqual(len(booze),1) - self.assertEqual(len(booze[0]),1) - self.assertEqual(booze[0][0],'Victoria Bitter') + self.assertEqual(len(booze), 1) + self.assertEqual(len(booze[0]), 1) + self.assertEqual(booze[0][0], "Victoria Bitter") finally: con.close() @@ -270,31 +254,41 @@ def test_description(self): try: cur = con.cursor() self.executeDDL1(cur) - self.assertEqual(cur.description,None, - 'cursor.description should be none after executing a ' - 'statement that can return no rows (such as DDL)' - ) - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(len(cur.description),1, - 'cursor.description describes too many columns' - ) - self.assertEqual(len(cur.description[0]),7, - 'cursor.description[x] tuples must have 7 elements' - ) - self.assertEqual(cur.description[0][0].lower(),'name', - 'cursor.description[x][0] must return column name' - ) - self.assertEqual(cur.description[0][1],self.driver.STRING, - 'cursor.description[x][1] must return column type. Got %r' - % cur.description[0][1] - ) + self.assertEqual( + cur.description, + None, + "cursor.description should be none after executing a " + "statement that can return no rows (such as DDL)", + ) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertEqual( + len(cur.description), 1, "cursor.description describes too many columns" + ) + self.assertEqual( + len(cur.description[0]), + 7, + "cursor.description[x] tuples must have 7 elements", + ) + self.assertEqual( + cur.description[0][0].lower(), + "name", + "cursor.description[x][0] must return column name", + ) + self.assertEqual( + cur.description[0][1], + self.driver.STRING, + "cursor.description[x][1] must return column type. Got %r" + % cur.description[0][1], + ) # Make sure self.description gets reset self.executeDDL2(cur) - self.assertEqual(cur.description,None, - 'cursor.description not being set to None when executing ' - 'no-result statements (eg. DDL)' - ) + self.assertEqual( + cur.description, + None, + "cursor.description not being set to None when executing " + "no-result statements (eg. DDL)", + ) finally: con.close() @@ -303,47 +297,49 @@ def test_rowcount(self): try: cur = con.cursor() self.executeDDL1(cur) - self.assertEqual(cur.rowcount,-1, - 'cursor.rowcount should be -1 after executing no-result ' - 'statements' - ) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number or rows inserted, or ' - 'set to -1 after executing an insert statement' - ) + self.assertEqual( + cur.rowcount, + -1, + "cursor.rowcount should be -1 after executing no-result " "statements", + ) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + self.assertTrue( + cur.rowcount in (-1, 1), + "cursor.rowcount should == number or rows inserted, or " + "set to -1 after executing an insert statement", + ) cur.execute("select name from %sbooze" % self.table_prefix) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number of rows returned, or ' - 'set to -1 after executing a select statement' - ) + self.assertTrue( + cur.rowcount in (-1, 1), + "cursor.rowcount should == number of rows returned, or " + "set to -1 after executing a select statement", + ) self.executeDDL2(cur) - self.assertEqual(cur.rowcount,-1, - 'cursor.rowcount not being reset to -1 after executing ' - 'no-result statements' - ) + self.assertEqual( + cur.rowcount, + -1, + "cursor.rowcount not being reset to -1 after executing " + "no-result statements", + ) finally: con.close() - lower_func = 'lower' + lower_func = "lower" + def test_callproc(self): con = self._connect() try: cur = con.cursor() - if self.lower_func and hasattr(cur,'callproc'): - r = cur.callproc(self.lower_func,('FOO',)) - self.assertEqual(len(r),1) - self.assertEqual(r[0],'FOO') + if self.lower_func and hasattr(cur, "callproc"): + r = cur.callproc(self.lower_func, ("FOO",)) + self.assertEqual(len(r), 1) + self.assertEqual(r[0], "FOO") r = cur.fetchall() - self.assertEqual(len(r),1,'callproc produced no result set') - self.assertEqual(len(r[0]),1, - 'callproc produced invalid result set' - ) - self.assertEqual(r[0][0],'foo', - 'callproc produced invalid results' - ) + self.assertEqual(len(r), 1, "callproc produced no result set") + self.assertEqual(len(r[0]), 1, "callproc produced invalid result set") + self.assertEqual(r[0][0], "foo", "callproc produced invalid results") finally: con.close() @@ -356,14 +352,14 @@ def test_close(self): # cursor.execute should raise an Error if called after connection # closed - self.assertRaises(self.driver.Error,self.executeDDL1,cur) + self.assertRaises(self.driver.Error, self.executeDDL1, cur) # connection.commit should raise an Error if called after connection' # closed.' - self.assertRaises(self.driver.Error,con.commit) + self.assertRaises(self.driver.Error, con.commit) # connection.close should raise an Error if called more than once - self.assertRaises(self.driver.Error,con.close) + self.assertRaises(self.driver.Error, con.close) def test_execute(self): con = self._connect() @@ -373,105 +369,99 @@ def test_execute(self): finally: con.close() - def _paraminsert(self,cur): + def _paraminsert(self, cur): self.executeDDL1(cur) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) - self.assertTrue(cur.rowcount in (-1,1)) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + self.assertTrue(cur.rowcount in (-1, 1)) - if self.driver.paramstyle == 'qmark': + if self.driver.paramstyle == "qmark": cur.execute( - 'insert into %sbooze values (?)' % self.table_prefix, - ("Cooper's",) - ) - elif self.driver.paramstyle == 'numeric': + "insert into %sbooze values (?)" % self.table_prefix, ("Cooper's",) + ) + elif self.driver.paramstyle == "numeric": cur.execute( - 'insert into %sbooze values (:1)' % self.table_prefix, - ("Cooper's",) - ) - elif self.driver.paramstyle == 'named': + "insert into %sbooze values (:1)" % self.table_prefix, ("Cooper's",) + ) + elif self.driver.paramstyle == "named": cur.execute( - 'insert into %sbooze values (:beer)' % self.table_prefix, - {'beer':"Cooper's"} - ) - elif self.driver.paramstyle == 'format': + "insert into %sbooze values (:beer)" % self.table_prefix, + {"beer": "Cooper's"}, + ) + elif self.driver.paramstyle == "format": cur.execute( - 'insert into %sbooze values (%%s)' % self.table_prefix, - ("Cooper's",) - ) - elif self.driver.paramstyle == 'pyformat': + "insert into %sbooze values (%%s)" % self.table_prefix, ("Cooper's",) + ) + elif self.driver.paramstyle == "pyformat": cur.execute( - 'insert into %sbooze values (%%(beer)s)' % self.table_prefix, - {'beer':"Cooper's"} - ) + "insert into %sbooze values (%%(beer)s)" % self.table_prefix, + {"beer": "Cooper's"}, + ) else: - self.fail('Invalid paramstyle') - self.assertTrue(cur.rowcount in (-1,1)) + self.fail("Invalid paramstyle") + self.assertTrue(cur.rowcount in (-1, 1)) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) res = cur.fetchall() - self.assertEqual(len(res),2,'cursor.fetchall returned too few rows') - beers = [res[0][0],res[1][0]] + self.assertEqual(len(res), 2, "cursor.fetchall returned too few rows") + beers = [res[0][0], res[1][0]] beers.sort() - self.assertEqual(beers[0],"Cooper's", - 'cursor.fetchall retrieved incorrect data, or data inserted ' - 'incorrectly' - ) - self.assertEqual(beers[1],"Victoria Bitter", - 'cursor.fetchall retrieved incorrect data, or data inserted ' - 'incorrectly' - ) + self.assertEqual( + beers[0], + "Cooper's", + "cursor.fetchall retrieved incorrect data, or data inserted " "incorrectly", + ) + self.assertEqual( + beers[1], + "Victoria Bitter", + "cursor.fetchall retrieved incorrect data, or data inserted " "incorrectly", + ) def test_executemany(self): con = self._connect() try: cur = con.cursor() self.executeDDL1(cur) - largs = [ ("Cooper's",) , ("Boag's",) ] - margs = [ {'beer': "Cooper's"}, {'beer': "Boag's"} ] - if self.driver.paramstyle == 'qmark': + largs = [("Cooper's",), ("Boag's",)] + margs = [{"beer": "Cooper's"}, {"beer": "Boag's"}] + if self.driver.paramstyle == "qmark": cur.executemany( - 'insert into %sbooze values (?)' % self.table_prefix, - largs - ) - elif self.driver.paramstyle == 'numeric': + "insert into %sbooze values (?)" % self.table_prefix, largs + ) + elif self.driver.paramstyle == "numeric": cur.executemany( - 'insert into %sbooze values (:1)' % self.table_prefix, - largs - ) - elif self.driver.paramstyle == 'named': + "insert into %sbooze values (:1)" % self.table_prefix, largs + ) + elif self.driver.paramstyle == "named": cur.executemany( - 'insert into %sbooze values (:beer)' % self.table_prefix, - margs - ) - elif self.driver.paramstyle == 'format': + "insert into %sbooze values (:beer)" % self.table_prefix, margs + ) + elif self.driver.paramstyle == "format": cur.executemany( - 'insert into %sbooze values (%%s)' % self.table_prefix, - largs - ) - elif self.driver.paramstyle == 'pyformat': + "insert into %sbooze values (%%s)" % self.table_prefix, largs + ) + elif self.driver.paramstyle == "pyformat": cur.executemany( - 'insert into %sbooze values (%%(beer)s)' % ( - self.table_prefix - ), - margs - ) - else: - self.fail('Unknown paramstyle') - self.assertTrue(cur.rowcount in (-1,2), - 'insert using cursor.executemany set cursor.rowcount to ' - 'incorrect value %r' % cur.rowcount + "insert into %sbooze values (%%(beer)s)" % (self.table_prefix), + margs, ) - cur.execute('select name from %sbooze' % self.table_prefix) + else: + self.fail("Unknown paramstyle") + self.assertTrue( + cur.rowcount in (-1, 2), + "insert using cursor.executemany set cursor.rowcount to " + "incorrect value %r" % cur.rowcount, + ) + cur.execute("select name from %sbooze" % self.table_prefix) res = cur.fetchall() - self.assertEqual(len(res),2, - 'cursor.fetchall retrieved incorrect number of rows' - ) - beers = [res[0][0],res[1][0]] + self.assertEqual( + len(res), 2, "cursor.fetchall retrieved incorrect number of rows" + ) + beers = [res[0][0], res[1][0]] beers.sort() - self.assertEqual(beers[0],"Boag's",'incorrect data retrieved') - self.assertEqual(beers[1],"Cooper's",'incorrect data retrieved') + self.assertEqual(beers[0], "Boag's", "incorrect data retrieved") + self.assertEqual(beers[1], "Cooper's", "incorrect data retrieved") finally: con.close() @@ -482,59 +472,62 @@ def test_fetchone(self): # cursor.fetchone should raise an Error if called before # executing a select-type query - self.assertRaises(self.driver.Error,cur.fetchone) + self.assertRaises(self.driver.Error, cur.fetchone) # cursor.fetchone should raise an Error if called after # executing a query that cannot return rows self.executeDDL1(cur) - self.assertRaises(self.driver.Error,cur.fetchone) + self.assertRaises(self.driver.Error, cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if a query retrieves ' - 'no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertEqual( + cur.fetchone(), + None, + "cursor.fetchone should return None if a query retrieves " "no rows", + ) + self.assertTrue(cur.rowcount in (-1, 0)) # cursor.fetchone should raise an Error if called after # executing a query that cannot return rows - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) - self.assertRaises(self.driver.Error,cur.fetchone) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + self.assertRaises(self.driver.Error, cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchone() - self.assertEqual(len(r),1, - 'cursor.fetchone should have retrieved a single row' - ) - self.assertEqual(r[0],'Victoria Bitter', - 'cursor.fetchone retrieved incorrect data' - ) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if no more rows available' - ) - self.assertTrue(cur.rowcount in (-1,1)) + self.assertEqual( + len(r), 1, "cursor.fetchone should have retrieved a single row" + ) + self.assertEqual( + r[0], "Victoria Bitter", "cursor.fetchone retrieved incorrect data" + ) + self.assertEqual( + cur.fetchone(), + None, + "cursor.fetchone should return None if no more rows available", + ) + self.assertTrue(cur.rowcount in (-1, 1)) finally: con.close() samples = [ - 'Carlton Cold', - 'Carlton Draft', - 'Mountain Goat', - 'Redback', - 'Victoria Bitter', - 'XXXX' - ] + "Carlton Cold", + "Carlton Draft", + "Mountain Goat", + "Redback", + "Victoria Bitter", + "XXXX", + ] def _populate(self): - ''' Return a list of sql commands to setup the DB for the fetch + """ Return a list of sql commands to setup the DB for the fetch tests. - ''' + """ populate = [ - "insert into %sbooze values ('%s')" % (self.table_prefix,s) - for s in self.samples - ] + "insert into {}booze values ('{}')".format(self.table_prefix, s) + for s in self.samples + ] return populate def test_fetchmany(self): @@ -543,78 +536,88 @@ def test_fetchmany(self): cur = con.cursor() # cursor.fetchmany should raise an Error if called without - #issuing a query - self.assertRaises(self.driver.Error,cur.fetchmany,4) + # issuing a query + self.assertRaises(self.driver.Error, cur.fetchmany, 4) self.executeDDL1(cur) for sql in self._populate(): cur.execute(sql) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchmany() - self.assertEqual(len(r),1, - 'cursor.fetchmany retrieved incorrect number of rows, ' - 'default of arraysize is one.' - ) - cur.arraysize=10 - r = cur.fetchmany(3) # Should get 3 rows - self.assertEqual(len(r),3, - 'cursor.fetchmany retrieved incorrect number of rows' - ) - r = cur.fetchmany(4) # Should get 2 more - self.assertEqual(len(r),2, - 'cursor.fetchmany retrieved incorrect number of rows' - ) - r = cur.fetchmany(4) # Should be an empty sequence - self.assertEqual(len(r),0, - 'cursor.fetchmany should return an empty sequence after ' - 'results are exhausted' + self.assertEqual( + len(r), + 1, + "cursor.fetchmany retrieved incorrect number of rows, " + "default of arraysize is one.", + ) + cur.arraysize = 10 + r = cur.fetchmany(3) # Should get 3 rows + self.assertEqual( + len(r), 3, "cursor.fetchmany retrieved incorrect number of rows" + ) + r = cur.fetchmany(4) # Should get 2 more + self.assertEqual( + len(r), 2, "cursor.fetchmany retrieved incorrect number of rows" ) - self.assertTrue(cur.rowcount in (-1,6)) + r = cur.fetchmany(4) # Should be an empty sequence + self.assertEqual( + len(r), + 0, + "cursor.fetchmany should return an empty sequence after " + "results are exhausted", + ) + self.assertTrue(cur.rowcount in (-1, 6)) # Same as above, using cursor.arraysize - cur.arraysize=4 - cur.execute('select name from %sbooze' % self.table_prefix) - r = cur.fetchmany() # Should get 4 rows - self.assertEqual(len(r),4, - 'cursor.arraysize not being honoured by fetchmany' - ) - r = cur.fetchmany() # Should get 2 more - self.assertEqual(len(r),2) - r = cur.fetchmany() # Should be an empty sequence - self.assertEqual(len(r),0) - self.assertTrue(cur.rowcount in (-1,6)) - - cur.arraysize=6 - cur.execute('select name from %sbooze' % self.table_prefix) - rows = cur.fetchmany() # Should get all rows - self.assertTrue(cur.rowcount in (-1,6)) - self.assertEqual(len(rows),6) - self.assertEqual(len(rows),6) + cur.arraysize = 4 + cur.execute("select name from %sbooze" % self.table_prefix) + r = cur.fetchmany() # Should get 4 rows + self.assertEqual( + len(r), 4, "cursor.arraysize not being honoured by fetchmany" + ) + r = cur.fetchmany() # Should get 2 more + self.assertEqual(len(r), 2) + r = cur.fetchmany() # Should be an empty sequence + self.assertEqual(len(r), 0) + self.assertTrue(cur.rowcount in (-1, 6)) + + cur.arraysize = 6 + cur.execute("select name from %sbooze" % self.table_prefix) + rows = cur.fetchmany() # Should get all rows + self.assertTrue(cur.rowcount in (-1, 6)) + self.assertEqual(len(rows), 6) + self.assertEqual(len(rows), 6) rows = [r[0] for r in rows] rows.sort() # Make sure we get the right data back out - for i in range(0,6): - self.assertEqual(rows[i],self.samples[i], - 'incorrect data retrieved by cursor.fetchmany' - ) - - rows = cur.fetchmany() # Should return an empty list - self.assertEqual(len(rows),0, - 'cursor.fetchmany should return an empty sequence if ' - 'called after the whole result set has been fetched' + for i in range(0, 6): + self.assertEqual( + rows[i], + self.samples[i], + "incorrect data retrieved by cursor.fetchmany", ) - self.assertTrue(cur.rowcount in (-1,6)) + + rows = cur.fetchmany() # Should return an empty list + self.assertEqual( + len(rows), + 0, + "cursor.fetchmany should return an empty sequence if " + "called after the whole result set has been fetched", + ) + self.assertTrue(cur.rowcount in (-1, 6)) self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) - r = cur.fetchmany() # Should get empty sequence - self.assertEqual(len(r),0, - 'cursor.fetchmany should return an empty sequence if ' - 'query retrieved no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) + cur.execute("select name from %sbarflys" % self.table_prefix) + r = cur.fetchmany() # Should get empty sequence + self.assertEqual( + len(r), + 0, + "cursor.fetchmany should return an empty sequence if " + "query retrieved no rows", + ) + self.assertTrue(cur.rowcount in (-1, 0)) finally: con.close() @@ -634,36 +637,41 @@ def test_fetchall(self): # cursor.fetchall should raise an Error if called # after executing a statement that cannot return rows - self.assertRaises(self.driver.Error,cur.fetchall) + self.assertRaises(self.driver.Error, cur.fetchall) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,len(self.samples))) - self.assertEqual(len(rows),len(self.samples), - 'cursor.fetchall did not retrieve all rows' - ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) + self.assertEqual( + len(rows), + len(self.samples), + "cursor.fetchall did not retrieve all rows", + ) rows = [r[0] for r in rows] rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'cursor.fetchall retrieved incorrect rows' + for i in range(0, len(self.samples)): + self.assertEqual( + rows[i], self.samples[i], "cursor.fetchall retrieved incorrect rows" ) rows = cur.fetchall() self.assertEqual( - len(rows),0, - 'cursor.fetchall should return an empty list if called ' - 'after the whole result set has been fetched' - ) - self.assertTrue(cur.rowcount in (-1,len(self.samples))) + len(rows), + 0, + "cursor.fetchall should return an empty list if called " + "after the whole result set has been fetched", + ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) + cur.execute("select name from %sbarflys" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,0)) - self.assertEqual(len(rows),0, - 'cursor.fetchall should return an empty list if ' - 'a select query returns no rows' - ) + self.assertTrue(cur.rowcount in (-1, 0)) + self.assertEqual( + len(rows), + 0, + "cursor.fetchall should return an empty list if " + "a select query returns no rows", + ) finally: con.close() @@ -676,91 +684,91 @@ def test_mixedfetch(self): for sql in self._populate(): cur.execute(sql) - cur.execute('select name from %sbooze' % self.table_prefix) - rows1 = cur.fetchone() + cur.execute("select name from %sbooze" % self.table_prefix) + rows1 = cur.fetchone() rows23 = cur.fetchmany(2) - rows4 = cur.fetchone() + rows4 = cur.fetchone() rows56 = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,6)) - self.assertEqual(len(rows23),2, - 'fetchmany returned incorrect number of rows' - ) - self.assertEqual(len(rows56),2, - 'fetchall returned incorrect number of rows' - ) + self.assertTrue(cur.rowcount in (-1, 6)) + self.assertEqual( + len(rows23), 2, "fetchmany returned incorrect number of rows" + ) + self.assertEqual( + len(rows56), 2, "fetchall returned incorrect number of rows" + ) rows = [rows1[0]] - rows.extend([rows23[0][0],rows23[1][0]]) + rows.extend([rows23[0][0], rows23[1][0]]) rows.append(rows4[0]) - rows.extend([rows56[0][0],rows56[1][0]]) + rows.extend([rows56[0][0], rows56[1][0]]) rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'incorrect data retrieved or inserted' - ) + for i in range(0, len(self.samples)): + self.assertEqual( + rows[i], self.samples[i], "incorrect data retrieved or inserted" + ) finally: con.close() - def help_nextset_setUp(self,cur): - ''' Should create a procedure called deleteme + def help_nextset_setUp(self, cur): + """ Should create a procedure called deleteme that returns two result sets, first the number of rows in booze then "name from booze" - ''' - raise NotImplementedError('Helper not implemented') - #sql=""" + """ + raise NotImplementedError("Helper not implemented") + # sql=""" # create procedure deleteme as # begin # select count(*) from booze # select name from booze # end - #""" - #cur.execute(sql) + # """ + # cur.execute(sql) - def help_nextset_tearDown(self,cur): - 'If cleaning up is needed after nextSetTest' - raise NotImplementedError('Helper not implemented') - #cur.execute("drop procedure deleteme") + def help_nextset_tearDown(self, cur): + "If cleaning up is needed after nextSetTest" + raise NotImplementedError("Helper not implemented") + # cur.execute("drop procedure deleteme") def test_nextset(self): con = self._connect() try: cur = con.cursor() - if not hasattr(cur,'nextset'): + if not hasattr(cur, "nextset"): return try: self.executeDDL1(cur) - sql=self._populate() + sql = self._populate() for sql in self._populate(): cur.execute(sql) self.help_nextset_setUp(cur) - cur.callproc('deleteme') - numberofrows=cur.fetchone() - assert numberofrows[0]== len(self.samples) + cur.callproc("deleteme") + numberofrows = cur.fetchone() + assert numberofrows[0] == len(self.samples) assert cur.nextset() - names=cur.fetchall() + names = cur.fetchall() assert len(names) == len(self.samples) - s=cur.nextset() - assert s == None,'No more return sets, should return None' + s = cur.nextset() + assert s is None, "No more return sets, should return None" finally: self.help_nextset_tearDown(cur) finally: con.close() - def test_nextset(self): - raise NotImplementedError('Drivers need to override this test') + def test_nextset(self): # noqa: F811 + raise NotImplementedError("Drivers need to override this test") def test_arraysize(self): # Not much here - rest of the tests for this are in test_fetchmany con = self._connect() try: cur = con.cursor() - self.assertTrue(hasattr(cur,'arraysize'), - 'cursor.arraysize must be defined' - ) + self.assertTrue( + hasattr(cur, "arraysize"), "cursor.arraysize must be defined" + ) finally: con.close() @@ -768,8 +776,8 @@ def test_setinputsizes(self): con = self._connect() try: cur = con.cursor() - cur.setinputsizes( (25,) ) - self._paraminsert(cur) # Make sure cursor still works + cur.setinputsizes((25,)) + self._paraminsert(cur) # Make sure cursor still works finally: con.close() @@ -779,75 +787,74 @@ def test_setoutputsize_basic(self): try: cur = con.cursor() cur.setoutputsize(1000) - cur.setoutputsize(2000,0) - self._paraminsert(cur) # Make sure the cursor still works + cur.setoutputsize(2000, 0) + self._paraminsert(cur) # Make sure the cursor still works finally: con.close() def test_setoutputsize(self): # Real test for setoutputsize is driver dependant - raise NotImplementedError('Driver need to override this test') + raise NotImplementedError("Driver need to override this test") def test_None(self): con = self._connect() try: cur = con.cursor() self.executeDDL1(cur) - cur.execute('insert into %sbooze values (NULL)' % self.table_prefix) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("insert into %sbooze values (NULL)" % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchall() - self.assertEqual(len(r),1) - self.assertEqual(len(r[0]),1) - self.assertEqual(r[0][0],None,'NULL value not returned as None') + self.assertEqual(len(r), 1) + self.assertEqual(len(r[0]), 1) + self.assertEqual(r[0][0], None, "NULL value not returned as None") finally: con.close() def test_Date(self): - d1 = self.driver.Date(2002,12,25) - d2 = self.driver.DateFromTicks(time.mktime((2002,12,25,0,0,0,0,0,0))) + d1 = self.driver.Date(2002, 12, 25) # noqa F841 + d2 = self.driver.DateFromTicks( # noqa F841 + time.mktime((2002, 12, 25, 0, 0, 0, 0, 0, 0)) + ) # Can we assume this? API doesn't specify, but it seems implied # self.assertEqual(str(d1),str(d2)) def test_Time(self): - t1 = self.driver.Time(13,45,30) - t2 = self.driver.TimeFromTicks(time.mktime((2001,1,1,13,45,30,0,0,0))) + t1 = self.driver.Time(13, 45, 30) # noqa F841 + t2 = self.driver.TimeFromTicks( # noqa F841 + time.mktime((2001, 1, 1, 13, 45, 30, 0, 0, 0)) + ) # Can we assume this? API doesn't specify, but it seems implied # self.assertEqual(str(t1),str(t2)) def test_Timestamp(self): - t1 = self.driver.Timestamp(2002,12,25,13,45,30) - t2 = self.driver.TimestampFromTicks( - time.mktime((2002,12,25,13,45,30,0,0,0)) - ) + t1 = self.driver.Timestamp(2002, 12, 25, 13, 45, 30) # noqa F841 + t2 = self.driver.TimestampFromTicks( # noqa F841 + time.mktime((2002, 12, 25, 13, 45, 30, 0, 0, 0)) + ) # Can we assume this? API doesn't specify, but it seems implied # self.assertEqual(str(t1),str(t2)) def test_Binary(self): - b = self.driver.Binary(b'Something') - b = self.driver.Binary(b'') + b = self.driver.Binary(b"Something") + b = self.driver.Binary(b"") # noqa F841 def test_STRING(self): - self.assertTrue(hasattr(self.driver,'STRING'), - 'module.STRING must be defined' - ) + self.assertTrue(hasattr(self.driver, "STRING"), "module.STRING must be defined") def test_BINARY(self): - self.assertTrue(hasattr(self.driver,'BINARY'), - 'module.BINARY must be defined.' - ) + self.assertTrue( + hasattr(self.driver, "BINARY"), "module.BINARY must be defined." + ) def test_NUMBER(self): - self.assertTrue(hasattr(self.driver,'NUMBER'), - 'module.NUMBER must be defined.' - ) + self.assertTrue( + hasattr(self.driver, "NUMBER"), "module.NUMBER must be defined." + ) def test_DATETIME(self): - self.assertTrue(hasattr(self.driver,'DATETIME'), - 'module.DATETIME must be defined.' - ) + self.assertTrue( + hasattr(self.driver, "DATETIME"), "module.DATETIME must be defined." + ) def test_ROWID(self): - self.assertTrue(hasattr(self.driver,'ROWID'), - 'module.ROWID must be defined.' - ) - + self.assertTrue(hasattr(self.driver, "ROWID"), "module.ROWID must be defined.") diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index d5be511f..fe9ef03e 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -1,23 +1,23 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import capabilities from datetime import timedelta from contextlib import closing import unittest import MySQLdb -from MySQLdb import cursors from configdb import connection_factory import warnings -warnings.filterwarnings('ignore') +warnings.filterwarnings("ignore") class test_MySQLdb(capabilities.DatabaseTest): db_module = MySQLdb connect_args = () - connect_kwargs = dict(use_unicode=True, sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL") + connect_kwargs = dict( + use_unicode=True, sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL" + ) create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8" leak_test = False @@ -25,97 +25,113 @@ def quote_identifier(self, ident): return "`%s`" % ident def test_TIME(self): - def generator(row,col): - return timedelta(0, row*8000) - self.check_data_integrity( - ('col1 TIME',), - generator) + def generator(row, col): + return timedelta(0, row * 8000) + + self.check_data_integrity(("col1 TIME",), generator) def test_TINYINT(self): # Number data def generator(row, col): - v = (row*row) % 256 + v = (row * row) % 256 if v > 127: - v = v-256 + v = v - 256 return v - self.check_data_integrity( - ('col1 TINYINT',), - generator) + + self.check_data_integrity(("col1 TINYINT",), generator) def test_stored_procedures(self): db = self.connection c = self.cursor - self.create_table(('pos INT', 'tree CHAR(20)')) - c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, - list(enumerate('ash birch cedar Lärche pine'.split()))) + self.create_table(("pos INT", "tree CHAR(20)")) + c.executemany( + "INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, + list(enumerate("ash birch cedar Lärche pine".split())), + ) db.commit() - c.execute(""" + c.execute( + """ CREATE PROCEDURE test_sp(IN t VARCHAR(255)) BEGIN SELECT pos FROM %s WHERE tree = t; END - """ % self.table) + """ + % self.table + ) db.commit() - c.callproc('test_sp', ('Lärche',)) + c.callproc("test_sp", ("Lärche",)) rows = c.fetchall() self.assertEqual(len(rows), 1) self.assertEqual(rows[0][0], 3) c.nextset() c.execute("DROP PROCEDURE test_sp") - c.execute('drop table %s' % (self.table)) + c.execute("drop table %s" % (self.table)) def test_small_CHAR(self): # Character data - def generator(row,col): - i = (row*col+62)%256 - if i == 62: return '' - if i == 63: return None + def generator(row, col): + i = (row * col + 62) % 256 + if i == 62: + return "" + if i == 63: + return None return chr(i) - self.check_data_integrity( - ('col1 char(1)','col2 char(1)'), - generator) + + self.check_data_integrity(("col1 char(1)", "col2 char(1)"), generator) def test_BIT(self): c = self.cursor try: - c.execute("""create table test_BIT ( + c.execute( + """create table test_BIT ( b3 BIT(3), b7 BIT(10), - b64 BIT(64))""") + b64 BIT(64))""" + ) - one64 = '1'*64 + one64 = "1" * 64 c.execute( "insert into test_BIT (b3, b7, b64)" - " VALUES (b'011', b'1111111111', b'%s')" - % one64) + " VALUES (b'011', b'1111111111', b'%s')" % one64 + ) c.execute("SELECT b3, b7, b64 FROM test_BIT") row = c.fetchone() - self.assertEqual(row[0], b'\x03') - self.assertEqual(row[1], b'\x03\xff') - self.assertEqual(row[2], b'\xff'*8) + self.assertEqual(row[0], b"\x03") + self.assertEqual(row[1], b"\x03\xff") + self.assertEqual(row[2], b"\xff" * 8) finally: c.execute("drop table if exists test_BIT") def test_MULTIPOLYGON(self): c = self.cursor try: - c.execute("""create table test_MULTIPOLYGON ( + c.execute( + """create table test_MULTIPOLYGON ( id INTEGER PRIMARY KEY, - border MULTIPOLYGON)""") + border MULTIPOLYGON)""" + ) c.execute( - "insert into test_MULTIPOLYGON (id, border)" - " VALUES (1, GeomFromText('MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))'))" + """ +INSERT INTO test_MULTIPOLYGON + (id, border) +VALUES (1, + Geomfromtext( +'MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))')) +""" ) c.execute("SELECT id, AsText(border) FROM test_MULTIPOLYGON") row = c.fetchone() self.assertEqual(row[0], 1) - self.assertEqual(row[1], 'MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))') + self.assertEqual( + row[1], + "MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))", + ) c.execute("SELECT id, AsWKB(border) FROM test_MULTIPOLYGON") row = c.fetchone() @@ -131,19 +147,21 @@ def test_MULTIPOLYGON(self): def test_bug_2671682(self): from MySQLdb.constants import ER + try: - self.cursor.execute("describe some_non_existent_table"); + self.cursor.execute("describe some_non_existent_table") except self.connection.ProgrammingError as msg: self.assertTrue(str(ER.NO_SUCH_TABLE) in str(msg)) def test_bug_3514287(self): c = self.cursor try: - c.execute("""create table bug_3541287 ( + c.execute( + """create table bug_3541287 ( c1 CHAR(10), - t1 TIMESTAMP)""") - c.execute("insert into bug_3541287 (c1,t1) values (%s, NOW())", - ("blah",)) + t1 TIMESTAMP)""" + ) + c.execute("insert into bug_3541287 (c1,t1) values (%s, NOW())", ("blah",)) finally: c.execute("drop table if exists bug_3541287") @@ -164,22 +182,25 @@ def test_binary_prefix(self): for binary_prefix in (True, False, None): kwargs = self.connect_kwargs.copy() # needs to be set to can guarantee CHARSET response for normal strings - kwargs['charset'] = 'utf8' - if binary_prefix != None: - kwargs['binary_prefix'] = binary_prefix + kwargs["charset"] = "utf8" + if binary_prefix is not None: + kwargs["binary_prefix"] = binary_prefix with closing(connection_factory(**kwargs)) as conn: with closing(conn.cursor()) as c: - c.execute('SELECT CHARSET(%s)', (MySQLdb.Binary(b'raw bytes'),)) - self.assertEqual(c.fetchall()[0][0], 'binary' if binary_prefix else 'utf8') + c.execute("SELECT CHARSET(%s)", (MySQLdb.Binary(b"raw bytes"),)) + self.assertEqual( + c.fetchall()[0][0], "binary" if binary_prefix else "utf8" + ) # normal strings should not get prefix - c.execute('SELECT CHARSET(%s)', ('str',)) - self.assertEqual(c.fetchall()[0][0], 'utf8') + c.execute("SELECT CHARSET(%s)", ("str",)) + self.assertEqual(c.fetchall()[0][0], "utf8") -if __name__ == '__main__': +if __name__ == "__main__": if test_MySQLdb.leak_test: import gc + gc.enable() gc.set_debug(gc.DEBUG_LEAK) unittest.main() diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 1e808bd1..6b3a3787 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -4,17 +4,22 @@ import MySQLdb from configdb import connection_kwargs import warnings + warnings.simplefilter("ignore") class test_MySQLdb(dbapi20.DatabaseAPI20Test): driver = MySQLdb connect_args = () - connect_kw_args = connection_kwargs(dict(sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")) + connect_kw_args = connection_kwargs( + dict(sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL") + ) + + def test_setoutputsize(self): + pass - def test_setoutputsize(self): pass - def test_setoutputsize_basic(self): pass - def test_nextset(self): pass + def test_setoutputsize_basic(self): + pass """The tests on fetchone and fetchall and rowcount bogusly test for an exception if the statement cannot return a @@ -36,36 +41,41 @@ def test_fetchall(self): # cursor.fetchall should raise an Error if called # after executing a statement that cannot return rows - #self.assertRaises(self.driver.Error,cur.fetchall) + # self.assertRaises(self.driver.Error,cur.fetchall) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,len(self.samples))) - self.assertEqual(len(rows),len(self.samples), - 'cursor.fetchall did not retrieve all rows' - ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) + self.assertEqual( + len(rows), + len(self.samples), + "cursor.fetchall did not retrieve all rows", + ) rows = [r[0] for r in rows] rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'cursor.fetchall retrieved incorrect rows' + for i in range(0, len(self.samples)): + self.assertEqual( + rows[i], self.samples[i], "cursor.fetchall retrieved incorrect rows" ) rows = cur.fetchall() self.assertEqual( - len(rows),0, - 'cursor.fetchall should return an empty list if called ' - 'after the whole result set has been fetched' - ) - self.assertTrue(cur.rowcount in (-1,len(self.samples))) + len(rows), + 0, + "cursor.fetchall should return an empty list if called " + "after the whole result set has been fetched", + ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) + cur.execute("select name from %sbarflys" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,0)) - self.assertEqual(len(rows),0, - 'cursor.fetchall should return an empty list if ' - 'a select query returns no rows' - ) + self.assertTrue(cur.rowcount in (-1, 0)) + self.assertEqual( + len(rows), + 0, + "cursor.fetchall should return an empty list if " + "a select query returns no rows", + ) finally: con.close() @@ -77,39 +87,42 @@ def test_fetchone(self): # cursor.fetchone should raise an Error if called before # executing a select-type query - self.assertRaises(self.driver.Error,cur.fetchone) + self.assertRaises(self.driver.Error, cur.fetchone) # cursor.fetchone should raise an Error if called after # executing a query that cannot return rows self.executeDDL1(cur) -## self.assertRaises(self.driver.Error,cur.fetchone) + # self.assertRaises(self.driver.Error,cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if a query retrieves ' - 'no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertEqual( + cur.fetchone(), + None, + "cursor.fetchone should return None if a query retrieves " "no rows", + ) + self.assertTrue(cur.rowcount in (-1, 0)) # cursor.fetchone should raise an Error if called after # executing a query that cannot return rows - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) -## self.assertRaises(self.driver.Error,cur.fetchone) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + # self.assertRaises(self.driver.Error,cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchone() - self.assertEqual(len(r),1, - 'cursor.fetchone should have retrieved a single row' - ) - self.assertEqual(r[0],'Victoria Bitter', - 'cursor.fetchone retrieved incorrect data' - ) -## self.assertEqual(cur.fetchone(),None, -## 'cursor.fetchone should return None if no more rows available' -## ) - self.assertTrue(cur.rowcount in (-1,1)) + self.assertEqual( + len(r), 1, "cursor.fetchone should have retrieved a single row" + ) + self.assertEqual( + r[0], "Victoria Bitter", "cursor.fetchone retrieved incorrect data" + ) + # self.assertEqual( + # cur.fetchone(), + # None, + # "cursor.fetchone should return None if no more rows available", + # ) + self.assertTrue(cur.rowcount in (-1, 1)) finally: con.close() @@ -119,81 +132,93 @@ def test_rowcount(self): try: cur = con.cursor() self.executeDDL1(cur) -## self.assertEqual(cur.rowcount,-1, -## 'cursor.rowcount should be -1 after executing no-result ' -## 'statements' -## ) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) -## self.assertTrue(cur.rowcount in (-1,1), -## 'cursor.rowcount should == number or rows inserted, or ' -## 'set to -1 after executing an insert statement' -## ) + # self.assertEqual(cur.rowcount,-1, + # 'cursor.rowcount should be -1 after executing no-result ' + # 'statements' + # ) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + # self.assertTrue(cur.rowcount in (-1,1), + # 'cursor.rowcount should == number or rows inserted, or ' + # 'set to -1 after executing an insert statement' + # ) cur.execute("select name from %sbooze" % self.table_prefix) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number of rows returned, or ' - 'set to -1 after executing a select statement' - ) + self.assertTrue( + cur.rowcount in (-1, 1), + "cursor.rowcount should == number of rows returned, or " + "set to -1 after executing a select statement", + ) self.executeDDL2(cur) -## self.assertEqual(cur.rowcount,-1, -## 'cursor.rowcount not being reset to -1 after executing ' -## 'no-result statements' -## ) + # self.assertEqual(cur.rowcount,-1, + # 'cursor.rowcount not being reset to -1 after executing ' + # 'no-result statements' + # ) finally: con.close() def test_callproc(self): - pass # performed in test_MySQL_capabilities + pass # performed in test_MySQL_capabilities - def help_nextset_setUp(self,cur): - ''' Should create a procedure called deleteme + def help_nextset_setUp(self, cur): + """ Should create a procedure called deleteme that returns two result sets, first the number of rows in booze then "name from booze" - ''' - sql=""" + """ + sql = """ create procedure deleteme() begin select count(*) from %(tp)sbooze; select name from %(tp)sbooze; end - """ % dict(tp=self.table_prefix) + """ % dict( + tp=self.table_prefix + ) cur.execute(sql) - def help_nextset_tearDown(self,cur): - 'If cleaning up is needed after nextSetTest' + def help_nextset_tearDown(self, cur): + "If cleaning up is needed after nextSetTest" cur.execute("drop procedure deleteme") def test_nextset(self): - #from warnings import warn + # from warnings import warn + con = self._connect() try: cur = con.cursor() - if not hasattr(cur, 'nextset'): + if not hasattr(cur, "nextset"): return try: self.executeDDL1(cur) - sql=self._populate() + sql = self._populate() for sql in self._populate(): cur.execute(sql) self.help_nextset_setUp(cur) - cur.callproc('deleteme') - numberofrows=cur.fetchone() - assert numberofrows[0]== len(self.samples) + cur.callproc("deleteme") + numberofrows = cur.fetchone() + assert numberofrows[0] == len(self.samples) assert cur.nextset() - names=cur.fetchall() + names = cur.fetchall() assert len(names) == len(self.samples) - s=cur.nextset() + s = cur.nextset() if s: empty = cur.fetchall() - self.assertEqual(len(empty), 0, - "non-empty result set after other result sets") - #warn("Incompatibility: MySQL returns an empty result set for the CALL itself", - # Warning) - #assert s == None,'No more return sets, should return None' + self.assertEqual( + len(empty), 0, "non-empty result set after other result sets" + ) + # warn( + # ": ".join( + # [ + # "Incompatibility", + # "MySQL returns an empty result set for the CALL itself" + # ] + # ), + # Warning, + # ) + # assert s == None, "No more return sets, should return None" finally: self.help_nextset_tearDown(cur) @@ -201,5 +226,5 @@ def test_nextset(self): con.close() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index c5cacbec..c517dad3 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -5,6 +5,7 @@ from MySQLdb.constants import FIELD_TYPE from configdb import connection_factory import warnings + warnings.simplefilter("ignore") @@ -36,10 +37,12 @@ def test_client_info(self): self.assertTrue(isinstance(_mysql.get_client_info(), str)) def test_escape_string(self): - self.assertEqual(_mysql.escape_string(b'foo"bar'), - b'foo\\"bar', "escape byte string") - self.assertEqual(_mysql.escape_string(u'foo"bar'), - b'foo\\"bar', "escape unicode string") + self.assertEqual( + _mysql.escape_string(b'foo"bar'), b'foo\\"bar', "escape byte string" + ) + self.assertEqual( + _mysql.escape_string('foo"bar'), b'foo\\"bar', "escape unicode string" + ) class CoreAPI(unittest.TestCase): @@ -53,42 +56,49 @@ def tearDown(self): def test_thread_id(self): tid = self.conn.thread_id() - self.assertTrue(isinstance(tid, int), - "thread_id didn't return an int.") + self.assertTrue(isinstance(tid, int), "thread_id didn't return an int.") - self.assertRaises(TypeError, self.conn.thread_id, ('evil',), - "thread_id shouldn't accept arguments.") + self.assertRaises( + TypeError, + self.conn.thread_id, + ("evil",), + "thread_id shouldn't accept arguments.", + ) def test_affected_rows(self): - self.assertEqual(self.conn.affected_rows(), 0, - "Should return 0 before we do anything.") - + self.assertEqual( + self.conn.affected_rows(), 0, "Should return 0 before we do anything." + ) - #def test_debug(self): - ## FIXME Only actually tests if you lack SUPER - #self.assertRaises(MySQLdb.OperationalError, - #self.conn.dump_debug_info) + # def test_debug(self): + # (FIXME) Only actually tests if you lack SUPER + # self.assertRaises(MySQLdb.OperationalError, + # self.conn.dump_debug_info) def test_charset_name(self): - self.assertTrue(isinstance(self.conn.character_set_name(), str), - "Should return a string.") + self.assertTrue( + isinstance(self.conn.character_set_name(), str), "Should return a string." + ) def test_host_info(self): - self.assertTrue(isinstance(self.conn.get_host_info(), str), - "Should return a string.") + self.assertTrue( + isinstance(self.conn.get_host_info(), str), "Should return a string." + ) def test_proto_info(self): - self.assertTrue(isinstance(self.conn.get_proto_info(), int), - "Should return an int.") + self.assertTrue( + isinstance(self.conn.get_proto_info(), int), "Should return an int." + ) def test_server_info(self): - self.assertTrue(isinstance(self.conn.get_server_info(), str), - "Should return a string.") + self.assertTrue( + isinstance(self.conn.get_server_info(), str), "Should return a string." + ) def test_client_flag(self): conn = connection_factory( - use_unicode=True, - client_flag=MySQLdb.constants.CLIENT.FOUND_ROWS) + use_unicode=True, client_flag=MySQLdb.constants.CLIENT.FOUND_ROWS + ) self.assertIsInstance(conn.client_flag, int) self.assertTrue(conn.client_flag & MySQLdb.constants.CLIENT.FOUND_ROWS) diff --git a/tests/test_MySQLdb_times.py b/tests/test_MySQLdb_times.py index d9d3e027..fdc35ff9 100644 --- a/tests/test_MySQLdb_times.py +++ b/tests/test_MySQLdb_times.py @@ -6,104 +6,141 @@ from MySQLdb import times import warnings + warnings.simplefilter("ignore") class TestX_or_None(unittest.TestCase): def test_date_or_none(self): - assert times.Date_or_None('1969-01-01') == date(1969, 1, 1) - assert times.Date_or_None('2015-01-01') == date(2015, 1, 1) - assert times.Date_or_None('2015-12-13') == date(2015, 12, 13) + assert times.Date_or_None("1969-01-01") == date(1969, 1, 1) + assert times.Date_or_None("2015-01-01") == date(2015, 1, 1) + assert times.Date_or_None("2015-12-13") == date(2015, 12, 13) - assert times.Date_or_None('') is None - assert times.Date_or_None('fail') is None - assert times.Date_or_None('2015-12') is None - assert times.Date_or_None('2015-12-40') is None - assert times.Date_or_None('0000-00-00') is None + assert times.Date_or_None("") is None + assert times.Date_or_None("fail") is None + assert times.Date_or_None("2015-12") is None + assert times.Date_or_None("2015-12-40") is None + assert times.Date_or_None("0000-00-00") is None def test_time_or_none(self): - assert times.Time_or_None('00:00:00') == time(0, 0) - assert times.Time_or_None('01:02:03') == time(1, 2, 3) - assert times.Time_or_None('01:02:03.123456') == time(1, 2, 3, 123456) + assert times.Time_or_None("00:00:00") == time(0, 0) + assert times.Time_or_None("01:02:03") == time(1, 2, 3) + assert times.Time_or_None("01:02:03.123456") == time(1, 2, 3, 123456) - assert times.Time_or_None('') is None - assert times.Time_or_None('fail') is None - assert times.Time_or_None('24:00:00') is None - assert times.Time_or_None('01:02:03.123456789') is None + assert times.Time_or_None("") is None + assert times.Time_or_None("fail") is None + assert times.Time_or_None("24:00:00") is None + assert times.Time_or_None("01:02:03.123456789") is None def test_datetime_or_none(self): - assert times.DateTime_or_None('1000-01-01') == date(1000, 1, 1) - assert times.DateTime_or_None('2015-12-13') == date(2015, 12, 13) - assert times.DateTime_or_None('2015-12-13 01:02') == datetime(2015, 12, 13, 1, 2) - assert times.DateTime_or_None('2015-12-13T01:02') == datetime(2015, 12, 13, 1, 2) - assert times.DateTime_or_None('2015-12-13 01:02:03') == datetime(2015, 12, 13, 1, 2, 3) - assert times.DateTime_or_None('2015-12-13T01:02:03') == datetime(2015, 12, 13, 1, 2, 3) - assert times.DateTime_or_None('2015-12-13 01:02:03.123') == datetime(2015, 12, 13, 1, 2, 3, 123000) - assert times.DateTime_or_None('2015-12-13 01:02:03.000123') == datetime(2015, 12, 13, 1, 2, 3, 123) - assert times.DateTime_or_None('2015-12-13 01:02:03.123456') == datetime(2015, 12, 13, 1, 2, 3, 123456) - assert times.DateTime_or_None('2015-12-13T01:02:03.123456') == datetime(2015, 12, 13, 1, 2, 3, 123456) - - assert times.DateTime_or_None('') is None - assert times.DateTime_or_None('fail') is None - assert times.DateTime_or_None('0000-00-00 00:00:00') is None - assert times.DateTime_or_None('0000-00-00 00:00:00.000000') is None - assert times.DateTime_or_None('2015-12-13T01:02:03.123456789') is None + assert times.DateTime_or_None("1000-01-01") == date(1000, 1, 1) + assert times.DateTime_or_None("2015-12-13") == date(2015, 12, 13) + assert times.DateTime_or_None("2015-12-13 01:02") == datetime( + 2015, 12, 13, 1, 2 + ) + assert times.DateTime_or_None("2015-12-13T01:02") == datetime( + 2015, 12, 13, 1, 2 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03") == datetime( + 2015, 12, 13, 1, 2, 3 + ) + assert times.DateTime_or_None("2015-12-13T01:02:03") == datetime( + 2015, 12, 13, 1, 2, 3 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03.123") == datetime( + 2015, 12, 13, 1, 2, 3, 123000 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03.000123") == datetime( + 2015, 12, 13, 1, 2, 3, 123 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03.123456") == datetime( + 2015, 12, 13, 1, 2, 3, 123456 + ) + assert times.DateTime_or_None("2015-12-13T01:02:03.123456") == datetime( + 2015, 12, 13, 1, 2, 3, 123456 + ) + + assert times.DateTime_or_None("") is None + assert times.DateTime_or_None("fail") is None + assert times.DateTime_or_None("0000-00-00 00:00:00") is None + assert times.DateTime_or_None("0000-00-00 00:00:00.000000") is None + assert times.DateTime_or_None("2015-12-13T01:02:03.123456789") is None def test_timedelta_or_none(self): - assert times.TimeDelta_or_None('-1:0:0') == timedelta(0, -3600) - assert times.TimeDelta_or_None('1:0:0') == timedelta(0, 3600) - assert times.TimeDelta_or_None('12:55:30') == timedelta(0, 46530) - assert times.TimeDelta_or_None('12:55:30.123456') == timedelta(0, 46530, 123456) - assert times.TimeDelta_or_None('12:55:30.123456789') == timedelta(0, 46653, 456789) - assert times.TimeDelta_or_None('12:55:30.123456789123456') == timedelta(1429, 37719, 123456) - - assert times.TimeDelta_or_None('') is None - assert times.TimeDelta_or_None('0') is None - assert times.TimeDelta_or_None('fail') is None + assert times.TimeDelta_or_None("-1:0:0") == timedelta(0, -3600) + assert times.TimeDelta_or_None("1:0:0") == timedelta(0, 3600) + assert times.TimeDelta_or_None("12:55:30") == timedelta(0, 46530) + assert times.TimeDelta_or_None("12:55:30.123456") == timedelta(0, 46530, 123456) + assert times.TimeDelta_or_None("12:55:30.123456789") == timedelta( + 0, 46653, 456789 + ) + assert times.TimeDelta_or_None("12:55:30.123456789123456") == timedelta( + 1429, 37719, 123456 + ) + + assert times.TimeDelta_or_None("") is None + assert times.TimeDelta_or_None("0") is None + assert times.TimeDelta_or_None("fail") is None class TestTicks(unittest.TestCase): - @mock.patch('MySQLdb.times.localtime', side_effect=gmtime) + @mock.patch("MySQLdb.times.localtime", side_effect=gmtime) def test_date_from_ticks(self, mock): assert times.DateFromTicks(0) == date(1970, 1, 1) assert times.DateFromTicks(1430000000) == date(2015, 4, 25) - @mock.patch('MySQLdb.times.localtime', side_effect=gmtime) + @mock.patch("MySQLdb.times.localtime", side_effect=gmtime) def test_time_from_ticks(self, mock): assert times.TimeFromTicks(0) == time(0, 0, 0) assert times.TimeFromTicks(1431100000) == time(15, 46, 40) assert times.TimeFromTicks(1431100000.123) == time(15, 46, 40) - @mock.patch('MySQLdb.times.localtime', side_effect=gmtime) + @mock.patch("MySQLdb.times.localtime", side_effect=gmtime) def test_timestamp_from_ticks(self, mock): assert times.TimestampFromTicks(0) == datetime(1970, 1, 1, 0, 0, 0) assert times.TimestampFromTicks(1430000000) == datetime(2015, 4, 25, 22, 13, 20) - assert times.TimestampFromTicks(1430000000.123) == datetime(2015, 4, 25, 22, 13, 20) + assert times.TimestampFromTicks(1430000000.123) == datetime( + 2015, 4, 25, 22, 13, 20 + ) class TestToLiteral(unittest.TestCase): def test_datetime_to_literal(self): - assert times.DateTime2literal(datetime(2015, 12, 13), '') == b"'2015-12-13 00:00:00'" - assert times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13), '') == b"'2015-12-13 11:12:13'" - assert times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13, 123456), '') == b"'2015-12-13 11:12:13.123456'" + self.assertEquals( + times.DateTime2literal(datetime(2015, 12, 13), ""), b"'2015-12-13 00:00:00'" + ) + self.assertEquals( + times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13), ""), + b"'2015-12-13 11:12:13'", + ) + self.assertEquals( + times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13, 123456), ""), + b"'2015-12-13 11:12:13.123456'", + ) def test_datetimedelta_to_literal(self): d = datetime(2015, 12, 13, 1, 2, 3) - datetime(2015, 12, 13, 1, 2, 2) - assert times.DateTimeDelta2literal(d, '') == b"'0 0:0:1'" + assert times.DateTimeDelta2literal(d, "") == b"'0 0:0:1'" class TestFormat(unittest.TestCase): def test_format_timedelta(self): d = datetime(2015, 1, 1) - datetime(2015, 1, 1) - assert times.format_TIMEDELTA(d) == '0 0:0:0' + assert times.format_TIMEDELTA(d) == "0 0:0:0" d = datetime(2015, 1, 1, 10, 11, 12) - datetime(2015, 1, 1, 8, 9, 10) - assert times.format_TIMEDELTA(d) == '0 2:2:2' + assert times.format_TIMEDELTA(d) == "0 2:2:2" d = datetime(2015, 1, 1, 10, 11, 12) - datetime(2015, 1, 1, 11, 12, 13) - assert times.format_TIMEDELTA(d) == '-1 22:58:59' + assert times.format_TIMEDELTA(d) == "-1 22:58:59" def test_format_timestamp(self): - assert times.format_TIMESTAMP(datetime(2015, 2, 3)) == '2015-02-03 00:00:00' - assert times.format_TIMESTAMP(datetime(2015, 2, 3, 17, 18, 19)) == '2015-02-03 17:18:19' - assert times.format_TIMESTAMP(datetime(15, 2, 3, 17, 18, 19)) == '0015-02-03 17:18:19' + assert times.format_TIMESTAMP(datetime(2015, 2, 3)) == "2015-02-03 00:00:00" + self.assertEquals( + times.format_TIMESTAMP(datetime(2015, 2, 3, 17, 18, 19)), + "2015-02-03 17:18:19", + ) + self.assertEquals( + times.format_TIMESTAMP(datetime(15, 2, 3, 17, 18, 19)), + "0015-02-03 17:18:19", + ) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index ff96368f..479f3e27 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,6 +1,4 @@ -from __future__ import print_function, absolute_import - -import pytest +# import pytest import MySQLdb.cursors from configdb import connection_factory @@ -8,6 +6,7 @@ _conns = [] _tables = [] + def connect(**kwargs): conn = connection_factory(**kwargs) _conns.append(conn) @@ -19,7 +18,7 @@ def teardown_function(function): c = _conns[0] cur = c.cursor() for t in _tables: - cur.execute("DROP TABLE %s" % (t,)) + cur.execute("DROP TABLE {}".format(t)) cur.close() del _tables[:] @@ -35,47 +34,71 @@ def test_executemany(): cursor.execute("create table test (data varchar(10))") _tables.append("test") - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%s, %s)") - assert m is not None, 'error parse %s' - assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' - - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id)s, %(name)s)") - assert m is not None, 'error parse %(name)s' - assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' - - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s)") - assert m is not None, 'error parse %(id_name)s' - assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' - - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s) ON duplicate update") - assert m is not None, 'error parse %(id_name)s' - assert m.group(3) == ' ON duplicate update', 'group 3 not ON duplicate update, bug in RE_INSERT_VALUES?' + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%s, %s)" + ) + assert m is not None, "error parse %s" + assert m.group(3) == "", "group 3 not blank, bug in RE_INSERT_VALUES?" + + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%(id)s, %(name)s)" + ) + assert m is not None, "error parse %(name)s" + assert m.group(3) == "", "group 3 not blank, bug in RE_INSERT_VALUES?" + + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s)" + ) + assert m is not None, "error parse %(id_name)s" + assert m.group(3) == "", "group 3 not blank, bug in RE_INSERT_VALUES?" + + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s) ON duplicate update" + ) + assert m is not None, "error parse %(id_name)s" + assert ( + m.group(3) == " ON duplicate update" + ), "group 3 not ON duplicate update, bug in RE_INSERT_VALUES?" # https://github.com/PyMySQL/mysqlclient-python/issues/178 - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO bloup(foo, bar)VALUES(%s, %s)") + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO bloup(foo, bar)VALUES(%s, %s)" + ) assert m is not None - # cursor._executed myst bee "insert into test (data) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)" + # cursor._executed myst bee + # """ + # insert into test (data) + # values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9) + # """ # list args data = range(10) cursor.executemany("insert into test (data) values (%s)", data) - assert cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %s not in one query' + assert cursor._executed.endswith( + b",(7),(8),(9)" + ), "execute many with %s not in one query" # dict args - data_dict = [{'data': i} for i in range(10)] + data_dict = [{"data": i} for i in range(10)] cursor.executemany("insert into test (data) values (%(data)s)", data_dict) - assert cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %(data)s not in one query' + assert cursor._executed.endswith( + b",(7),(8),(9)" + ), "execute many with %(data)s not in one query" # %% in column set - cursor.execute("""\ + cursor.execute( + """\ CREATE TABLE percent_test ( `A%` INTEGER, - `B%` INTEGER)""") + `B%` INTEGER)""" + ) try: q = "INSERT INTO percent_test (`A%%`, `B%%`) VALUES (%s, %s)" assert MySQLdb.cursors.RE_INSERT_VALUES.match(q) is not None cursor.executemany(q, [(3, 4), (5, 6)]) - assert cursor._executed.endswith(b"(3, 4),(5, 6)"), "executemany with %% not in one query" + assert cursor._executed.endswith( + b"(3, 4),(5, 6)" + ), "executemany with %% not in one query" finally: cursor.execute("DROP TABLE IF EXISTS percent_test") @@ -84,7 +107,7 @@ def test_pyparam(): conn = connect() cursor = conn.cursor() - cursor.execute(u"SELECT %(a)s, %(b)s", {u'a': 1, u'b': 2}) + cursor.execute("SELECT %(a)s, %(b)s", {"a": 1, "b": 2}) assert cursor._executed == b"SELECT 1, 2" - cursor.execute(b"SELECT %(a)s, %(b)s", {b'a': 3, b'b': 4}) + cursor.execute(b"SELECT %(a)s, %(b)s", {b"a": 3, b"b": 4}) assert cursor._executed == b"SELECT 3, 4" From c67dbd41f13f5302120ad40fee94ea6c7ffc2bfc Mon Sep 17 00:00:00 2001 From: Jason Fried Date: Wed, 5 Feb 2020 03:38:25 -0800 Subject: [PATCH 227/396] Add some gc safety around _mysql__fetch_row (#348) Users of gc.get_referrers() could cause a SystemError to be raised if this function is running in a different python thread. --- MySQLdb/_mysql.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 13280ace..1556fda3 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1373,9 +1373,15 @@ _mysql_ResultObject_fetch_row( convert_row = row_converters[how]; if (maxrows) { if (!(r = PyTuple_New(maxrows))) goto error; - rowsadded = _mysql__fetch_row(self, &r, skiprows, maxrows, - convert_row); + + // see: https://docs.python.org/3/library/gc.html#gc.get_referrers + // This function can get a reference to the tuple r, and if that + // code is preempted while holding a ref to r, the _PyTuple_Resize + // will raise a SystemError because the ref count is 2. + PyObject_GC_UnTrack(r); + rowsadded = _mysql__fetch_row(self, &r, skiprows, maxrows, convert_row); if (rowsadded == -1) goto error; + PyObject_GC_Track(r); } else { if (self->use) { maxrows = 1000; From 4c1950148ba10e23017bd5190f9da6bdb347e6b3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 17 Feb 2020 16:01:43 +0900 Subject: [PATCH 228/396] travis: Use bionic (#424) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea1c62ea..2f956c2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ -dist: xenial +dist: bionic language: python -# See aws s3 ls s3://travis-python-archives/binaries/ubuntu/16.04/x86_64/ +# See aws s3 ls s3://travis-python-archives/binaries/ubuntu/18.04/x86_64/ python: - "nightly" - "pypy3" From d8db1c905713e5b057bb33d213e64c0487085dd4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 12:59:10 +0900 Subject: [PATCH 229/396] Add Windows workflow --- .github/workflows/windows.yaml | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/windows.yaml diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml new file mode 100644 index 00000000..c66d4708 --- /dev/null +++ b/.github/workflows/windows.yaml @@ -0,0 +1,90 @@ +name: Build windows wheels + +on: + push: + branches: + - master + create: + +jobs: + build: + runs-on: windows-latest + env: + CONNECTOR_VERSION: "3.1.9" + steps: + + - name: Cache Connector + id: cache-connector + uses: actions/cache@v1 + with: + path: c:/mariadb-connector + key: mariadb-connector-${CONNECTOR_VERSION}-win + + - name: Download and Unzip Connector + if: steps.cache-connector.outputs.cache-hit != 'true' + shell: bash + run: | + curl -LO "https://downloads.mariadb.com/Connectors/c/connector-c-${CONNECTOR_VERSION}/mariadb-connector-c-${CONNECTOR_VERSION}-src.zip" + unzip "mariadb-connector-c-${CONNECTOR_VERSION}-src.zip" -d c:/ + mv "c:/mariadb-connector-c-${CONNECTOR_VERSION}-src" c:/mariadb-connector-src + + - name: Build Connector + if: steps.cache-connector.outputs.cache-hit != 'true' + shell: cmd + working-directory: c:/mariadb-connector-src + run: | + mkdir build + cd build + cmake -A x64 .. -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static + cmake --build . -j 8 --config Release + cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake + + - name: Checkout mysqlclient + uses: actions/checkout@v1 + with: + ref: master + fetch-depth: 10 + path: mysqlclient + + - name: Site Config + shell: bash + working-directory: ../mysqlclient + run: | + pwd + find . + cat <site.cfg + [options] + static = True + connector = C:/mariadb-connector + EOF + cat site.cfg + + - name: Build wheels + shell: cmd + working-directory: ../mysqlclient + run: | + py -3.8 -m pip install -U setuptools wheel pip + py -3.8 setup.py bdist_wheel + py -3.7 -m pip install -U setuptools wheel pip + py -3.7 setup.py bdist_wheel + py -3.6 -m pip install -U setuptools wheel pip + py -3.6 setup.py bdist_wheel + + - name: Upload Wheel + uses: actions/upload-artifact@v1 + with: + name: win-wheels + path: ../mysqlclient/dist + + - name: Check wheels + shell: bash + working-directory: ../mysqlclient/dist + run: | + ls -la + py -3.8 -m pip install mysqlclient-1.4.6-cp38-cp38-win_amd64.whl + py -3.8 -c "import MySQLdb" + py -3.7 -m pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl + py -3.7 -c "import MySQLdb" + py -3.6 -m pip install mysqlclient-1.4.6-cp36-cp36m-win_amd64.whl + py -3.6 -c "import MySQLdb" + From 6d0f35e608228af55040981de07dabe3e52f7b21 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 13:14:08 +0900 Subject: [PATCH 230/396] Update HISTORY --- HISTORY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.rst b/HISTORY.rst index 25678e55..f07c8a91 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,6 +7,7 @@ Release: TBD * Dropped Python 2 support * Dropped Django 1.11 support * Add context manager interface to Connection which closes the connection on ``__exit__``. +* Add ``ssl_mode`` option. ====================== From 7d7d43302653b0ebcd28ddd241863efecffc831f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 13:21:51 +0900 Subject: [PATCH 231/396] Add crypt32.lib --- setup_windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup_windows.py b/setup_windows.py index 917eb49d..030d01a4 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -18,7 +18,7 @@ def get_config(): vcversion = int(get_build_version()) if client == "mariadbclient": library_dirs = [os.path.join(connector, "lib", "mariadb")] - libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", client] + libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", "crypt32", client] include_dirs = [os.path.join(connector, "include", "mariadb")] else: library_dirs = [ From 4dff01a44a5326ea558282631d94ff5ccb82494a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 13:35:16 +0900 Subject: [PATCH 232/396] win: Add secur32.lib --- setup_windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup_windows.py b/setup_windows.py index 030d01a4..e0753949 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -18,7 +18,7 @@ def get_config(): vcversion = int(get_build_version()) if client == "mariadbclient": library_dirs = [os.path.join(connector, "lib", "mariadb")] - libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", "crypt32", client] + libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", "crypt32", "secur32", client] include_dirs = [os.path.join(connector, "include", "mariadb")] else: library_dirs = [ From dfe6b1b33f86bc0853964dd9f19d9d35a6442748 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 13:40:31 +0900 Subject: [PATCH 233/396] win: Add bcrypt.lib --- setup_windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup_windows.py b/setup_windows.py index e0753949..ace63bc3 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -18,7 +18,7 @@ def get_config(): vcversion = int(get_build_version()) if client == "mariadbclient": library_dirs = [os.path.join(connector, "lib", "mariadb")] - libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", "crypt32", "secur32", client] + libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", "crypt32", "secur32", "bcrypt", client] include_dirs = [os.path.join(connector, "include", "mariadb")] else: library_dirs = [ From bb7acab604ae7ce22d12e983007968f352425858 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 13:55:08 +0900 Subject: [PATCH 234/396] 2.0.0rc1 --- .github/workflows/windows.yaml | 6 +++--- metadata.cfg | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index c66d4708..166ea974 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -81,10 +81,10 @@ jobs: working-directory: ../mysqlclient/dist run: | ls -la - py -3.8 -m pip install mysqlclient-1.4.6-cp38-cp38-win_amd64.whl + py -3.8 -m pip install mysqlclient-2.0.0.rc1-cp38-cp38-win_amd64.whl py -3.8 -c "import MySQLdb" - py -3.7 -m pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl + py -3.7 -m pip install mysqlclient-2.0.0.rc1-cp37-cp37m-win_amd64.whl py -3.7 -c "import MySQLdb" - py -3.6 -m pip install mysqlclient-1.4.6-cp36-cp36m-win_amd64.whl + py -3.6 -m pip install mysqlclient-2.0.0.rc1-cp36-cp36m-win_amd64.whl py -3.6 -c "import MySQLdb" diff --git a/metadata.cfg b/metadata.cfg index bae8bd44..a5d7f3ae 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.0.0dev1 -version_info: (2,0,0,'dev',1) +version: 2.0.0.rc1 +version_info: (2,0,0,'rc',1) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 87a2635b436ab6a3298d789ab48081c6f850acfa Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 14:01:29 +0900 Subject: [PATCH 235/396] check --- .github/workflows/windows.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 166ea974..84f3a5ee 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -81,10 +81,10 @@ jobs: working-directory: ../mysqlclient/dist run: | ls -la - py -3.8 -m pip install mysqlclient-2.0.0.rc1-cp38-cp38-win_amd64.whl + py -3.8 -m pip install --no-index --find-links . mysqlclient py -3.8 -c "import MySQLdb" - py -3.7 -m pip install mysqlclient-2.0.0.rc1-cp37-cp37m-win_amd64.whl + py -3.7 -m pip install --no-index --find-links . mysqlclient py -3.7 -c "import MySQLdb" - py -3.6 -m pip install mysqlclient-2.0.0.rc1-cp36-cp36m-win_amd64.whl + py -3.6 -m pip install --no-index --find-links . mysqlclient py -3.6 -c "import MySQLdb" From d11ea2fcf04b7a129aa5953278740c484967edf8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 14:06:03 +0900 Subject: [PATCH 236/396] action: Print version_info --- .github/workflows/windows.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 84f3a5ee..4b62655c 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -82,9 +82,9 @@ jobs: run: | ls -la py -3.8 -m pip install --no-index --find-links . mysqlclient - py -3.8 -c "import MySQLdb" + py -3.8 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.7 -m pip install --no-index --find-links . mysqlclient - py -3.7 -c "import MySQLdb" + py -3.7 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.6 -m pip install --no-index --find-links . mysqlclient - py -3.6 -c "import MySQLdb" + py -3.6 -c "import MySQLdb; print(MySQLdb.version_info)" From 1145c72f64f5ce6592055549da297e883e8dd081 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 14:07:42 +0900 Subject: [PATCH 237/396] 2.0.0 --- HISTORY.rst | 2 +- metadata.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index f07c8a91..f6aa9f83 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,7 +2,7 @@ What's new in 2.0.0 ====================== -Release: TBD +Release: 2020-07-02 * Dropped Python 2 support * Dropped Django 1.11 support diff --git a/metadata.cfg b/metadata.cfg index a5d7f3ae..f672193a 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.0.0.rc1 -version_info: (2,0,0,'rc',1) +version: 2.0.0 +version_info: (2,0,0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 30c23f2cf4a38eda4844bea1968ace2f0c7c95e8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 14:10:37 +0900 Subject: [PATCH 238/396] black --- metadata.cfg | 2 +- setup_windows.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index f672193a..1c122c18 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] version: 2.0.0 -version_info: (2,0,0) +version_info: (2,0,0,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com diff --git a/setup_windows.py b/setup_windows.py index ace63bc3..c374ad60 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -18,7 +18,17 @@ def get_config(): vcversion = int(get_build_version()) if client == "mariadbclient": library_dirs = [os.path.join(connector, "lib", "mariadb")] - libraries = ["kernel32", "advapi32", "wsock32", "shlwapi", "Ws2_32", "crypt32", "secur32", "bcrypt", client] + libraries = [ + "kernel32", + "advapi32", + "wsock32", + "shlwapi", + "Ws2_32", + "crypt32", + "secur32", + "bcrypt", + client, + ] include_dirs = [os.path.join(connector, "include", "mariadb")] else: library_dirs = [ From 0887495b53c7a2528bf43f50e134e6e7d6ab67e0 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 2 Jul 2020 14:20:54 +0900 Subject: [PATCH 239/396] fix DeprecationWarning in test --- tests/test_MySQLdb_times.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_MySQLdb_times.py b/tests/test_MySQLdb_times.py index fdc35ff9..0947f3e5 100644 --- a/tests/test_MySQLdb_times.py +++ b/tests/test_MySQLdb_times.py @@ -106,14 +106,14 @@ def test_timestamp_from_ticks(self, mock): class TestToLiteral(unittest.TestCase): def test_datetime_to_literal(self): - self.assertEquals( + self.assertEqual( times.DateTime2literal(datetime(2015, 12, 13), ""), b"'2015-12-13 00:00:00'" ) - self.assertEquals( + self.assertEqual( times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13), ""), b"'2015-12-13 11:12:13'", ) - self.assertEquals( + self.assertEqual( times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13, 123456), ""), b"'2015-12-13 11:12:13.123456'", ) @@ -136,11 +136,11 @@ def test_format_timedelta(self): def test_format_timestamp(self): assert times.format_TIMESTAMP(datetime(2015, 2, 3)) == "2015-02-03 00:00:00" - self.assertEquals( + self.assertEqual( times.format_TIMESTAMP(datetime(2015, 2, 3, 17, 18, 19)), "2015-02-03 17:18:19", ) - self.assertEquals( + self.assertEqual( times.format_TIMESTAMP(datetime(15, 2, 3, 17, 18, 19)), "0015-02-03 17:18:19", ) From a825b849ff5499e07e16c3f7fc495830c1a7c628 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Jul 2020 11:20:38 +0900 Subject: [PATCH 240/396] Don't use PyTuple_Resize (#436) --- .travis.yml | 5 ++-- MySQLdb/_mysql.c | 78 +++++++++++++++++++----------------------------- 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f956c2d..ec1cd379 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ language: python python: - "nightly" - "pypy3" + - "3.9-dev" - "3.8" - "3.7" - "3.6" @@ -42,7 +43,7 @@ jobs: name: "Django 2.2 test" env: - DJANGO_VERSION=2.2.7 - python: "3.5" + python: "3.8" install: - pip install -U pip - wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz @@ -58,7 +59,7 @@ jobs: script: - cd django-${DJANGO_VERSION}/tests/ - - ./runtests.py --parallel=1 --settings=test_mysql + - ./runtests.py --parallel=2 --settings=test_mysql - name: flake8 python: "3.8" install: diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 1556fda3..62d0969d 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1298,19 +1298,16 @@ _mysql_row_to_dict_old( typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); -int +Py_ssize_t _mysql__fetch_row( _mysql_ResultObject *self, - PyObject **r, - int skiprows, - int maxrows, + PyObject *r, /* list object */ + Py_ssize_t maxrows, _PYFUNC *convert_row) { - int i; - MYSQL_ROW row; - - for (i = skiprows; i<(skiprows+maxrows); i++) { - PyObject *v; + Py_ssize_t i; + for (i = 0; i < maxrows; i++) { + MYSQL_ROW row; if (!self->use) row = mysql_fetch_row(self->result); else { @@ -1320,19 +1317,20 @@ _mysql__fetch_row( } if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { _mysql_Exception((_mysql_ConnectionObject *)self->conn); - goto error; + return -1; } if (!row) { - if (_PyTuple_Resize(r, i) == -1) goto error; break; } - v = convert_row(self, row); - if (!v) goto error; - PyTuple_SET_ITEM(*r, i, v); + PyObject *v = convert_row(self, row); + if (!v) return -1; + if (PyList_Append(r, v)) { + Py_DECREF(v); + return -1; + } + Py_DECREF(v); } - return i-skiprows; - error: - return -1; + return i; } static char _mysql_ResultObject_fetch_row__doc__[] = @@ -1359,7 +1357,7 @@ _mysql_ResultObject_fetch_row( _mysql_row_to_dict_old }; _PYFUNC *convert_row; - int maxrows=1, how=0, skiprows=0, rowsadded; + int maxrows=1, how=0; PyObject *r=NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, @@ -1371,40 +1369,24 @@ _mysql_ResultObject_fetch_row( return NULL; } convert_row = row_converters[how]; - if (maxrows) { - if (!(r = PyTuple_New(maxrows))) goto error; - - // see: https://docs.python.org/3/library/gc.html#gc.get_referrers - // This function can get a reference to the tuple r, and if that - // code is preempted while holding a ref to r, the _PyTuple_Resize - // will raise a SystemError because the ref count is 2. - PyObject_GC_UnTrack(r); - rowsadded = _mysql__fetch_row(self, &r, skiprows, maxrows, convert_row); - if (rowsadded == -1) goto error; - PyObject_GC_Track(r); - } else { + if (!maxrows) { if (self->use) { - maxrows = 1000; - if (!(r = PyTuple_New(maxrows))) goto error; - while (1) { - rowsadded = _mysql__fetch_row(self, &r, skiprows, - maxrows, convert_row); - if (rowsadded == -1) goto error; - skiprows += rowsadded; - if (rowsadded < maxrows) break; - if (_PyTuple_Resize(&r, skiprows+maxrows) == -1) - goto error; - } + maxrows = INT_MAX; } else { - /* XXX if overflow, maxrows<0? */ - maxrows = (int) mysql_num_rows(self->result); - if (!(r = PyTuple_New(maxrows))) goto error; - rowsadded = _mysql__fetch_row(self, &r, 0, - maxrows, convert_row); - if (rowsadded == -1) goto error; + // todo: preallocate. + maxrows = (Py_ssize_t) mysql_num_rows(self->result); } } - return r; + if (!(r = PyList_New(0))) goto error; + Py_ssize_t rowsadded = _mysql__fetch_row(self, r, maxrows, convert_row); + if (rowsadded == -1) goto error; + + /* DB-API allows return rows as list. + * But we need to return list because Django expecting tuple. + */ + PyObject *t = PyList_AsTuple(r); + Py_DECREF(r); + return t; error: Py_XDECREF(r); return NULL; From 143129be8f57d3a0667f01c989b9776bd80c28d3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Jul 2020 11:56:34 +0900 Subject: [PATCH 241/396] Remove obsolete members (#437) --- HISTORY.rst | 9 +++++++++ MySQLdb/cursors.py | 7 ------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index f6aa9f83..961e27ce 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,12 @@ +====================== + What's new in 2.0.1 +====================== + +Release: 2020-07-03 + +* Fixed multithread safety issue in fetching row. + + ====================== What's new in 2.0.0 ====================== diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 1d2ee460..451dab5f 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -71,9 +71,7 @@ def __init__(self, connection): self._executed = None self.lastrowid = None - self.messages = [] self._result = None - self._warnings = None self.rownumber = None self._rows = None @@ -134,7 +132,6 @@ def nextset(self): """ if self._executed: self.fetchall() - del self.messages[:] db = self._get_db() nr = db.next_result() @@ -155,7 +152,6 @@ def _do_get_result(self, db): self.rowcount = db.affected_rows() self.rownumber = 0 self.lastrowid = db.insert_id() - self._warnings = None def _post_get_result(self): pass @@ -222,8 +218,6 @@ def executemany(self, query, args): REPLACE. Otherwise it is equivalent to looping over args with execute(). """ - del self.messages[:] - if not args: return @@ -326,7 +320,6 @@ def _query(self, q): self._do_get_result(db) self._post_get_result() self._executed = q - self._last_executed = q # XXX THIS IS GARBAGE: See above. return self.rowcount def _fetch_row(self, size=1): From 2f69ec6ed4ef722b1da7929d73af302d58cb35a4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Jul 2020 12:22:58 +0900 Subject: [PATCH 242/396] update HISTORY --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 961e27ce..778b6431 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,7 +5,7 @@ Release: 2020-07-03 * Fixed multithread safety issue in fetching row. - +* Removed obsolete members from Cursor. (e.g. `messages`, `_warnings`, `_last_executed`) ====================== What's new in 2.0.0 From 5272dfc007c107f89a5cd1cdc0dab049c6039bad Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Jul 2020 12:23:20 +0900 Subject: [PATCH 243/396] v2.0.1 --- metadata.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index 1c122c18..527a2c7b 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.0.0 -version_info: (2,0,0,'final',0) +version: 2.0.1 +version_info: (2,0,1,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From bcb96c924b289905dced514d3c7dc9d81e919704 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 4 Jul 2020 09:51:31 +0900 Subject: [PATCH 244/396] codecov: Ignore constants --- MySQLdb/codecov.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 MySQLdb/codecov.yml diff --git a/MySQLdb/codecov.yml b/MySQLdb/codecov.yml new file mode 100644 index 00000000..174a4994 --- /dev/null +++ b/MySQLdb/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "MySQLdb/constants/*" From ca630c01fb39f252a4c91a525e440beea4ac4447 Mon Sep 17 00:00:00 2001 From: lhchavez Date: Fri, 3 Jul 2020 17:52:49 -0700 Subject: [PATCH 245/396] Update the documentation for `Cursor` (#438) This change updates the documentation for `Cursor`, since it does not use `CursorWarningMixIn` anymore. --- doc/user_guide.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index e52d0f79..83b800e8 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -674,10 +674,9 @@ CursorDictRowsMixIn Cursor The default cursor class. This class is composed of - ``CursorWarningMixIn``, ``CursorStoreResultMixIn``, - ``CursorTupleRowsMixIn,`` and ``BaseCursor``, i.e. it raises - ``Warning``, uses ``mysql_store_result()``, and returns rows as - tuples. + ``CursorStoreResultMixIn``, ``CursorTupleRowsMixIn``, and + ``BaseCursor``, i.e. uses ``mysql_store_result()`` and returns + rows as tuples. DictCursor Like ``Cursor`` except it returns rows as dictionaries. From feca95e3cdaf22f68f47248026fe17552cc4d565 Mon Sep 17 00:00:00 2001 From: SuperVirus Date: Fri, 4 Dec 2020 02:10:00 +0100 Subject: [PATCH 246/396] GitHub Action: Add Python 3.9 and Update MariaDB Connector C 3.1.11 (#453) --- .github/workflows/windows.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 4b62655c..55a013b0 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -5,12 +5,13 @@ on: branches: - master create: + workflow_dispatch: jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.1.9" + CONNECTOR_VERSION: "3.1.11" steps: - name: Cache Connector @@ -63,6 +64,8 @@ jobs: shell: cmd working-directory: ../mysqlclient run: | + py -3.9 -m pip install -U setuptools wheel pip + py -3.9 setup.py bdist_wheel py -3.8 -m pip install -U setuptools wheel pip py -3.8 setup.py bdist_wheel py -3.7 -m pip install -U setuptools wheel pip @@ -81,6 +84,8 @@ jobs: working-directory: ../mysqlclient/dist run: | ls -la + py -3.9 -m pip install --no-index --find-links . mysqlclient + py -3.9 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.8 -m pip install --no-index --find-links . mysqlclient py -3.8 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.7 -m pip install --no-index --find-links . mysqlclient From d7b988ae16cbe37dd9d342f92b2576c1815fbd23 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 4 Dec 2020 10:54:58 +0900 Subject: [PATCH 247/396] update issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 650b04b6..50d2bdc2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: '' ### Read this first -We don't use this issue tracker to help users. If you had trouble, please ask it on some user community. +We don't use this issue tracker to help users. If you had trouble, please ask it on some user community. See [here](https://github.com/PyMySQL/mysqlclient-python#support). Please use this tracker only when you are sure about it is an issue of this software. And please provide full information from first. I don't want to ask questions like "What is your Python version?", "Do you confirm MySQL error log?". If the issue report looks incomplete, I will just close it. From d56b0b7f8cc9131d4857b2a0b07e30e21aa6f26f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 4 Dec 2020 12:21:41 +0900 Subject: [PATCH 248/396] black 20.8b1 --- MySQLdb/times.py | 6 ++++- tests/capabilities.py | 10 +++---- tests/configdb.py | 5 +++- tests/dbapi20.py | 50 +++++++++++++++++------------------ tests/test_MySQLdb_dbapi20.py | 7 ++--- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/MySQLdb/times.py b/MySQLdb/times.py index f0e9384c..915d827b 100644 --- a/MySQLdb/times.py +++ b/MySQLdb/times.py @@ -131,7 +131,11 @@ def Time_or_None(s): def Date_or_None(s): try: - return date(int(s[:4]), int(s[5:7]), int(s[8:10]),) # year # month # day + return date( + int(s[:4]), + int(s[5:7]), + int(s[8:10]), + ) # year # month # day except ValueError: return None diff --git a/tests/capabilities.py b/tests/capabilities.py index cafe1e61..da753d15 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -68,12 +68,12 @@ def new_table_name(self): def create_table(self, columndefs): - """ Create a table using a list of column definitions given in - columndefs. + """Create a table using a list of column definitions given in + columndefs. - generator must be a function taking arguments (row_number, - col_number) returning a suitable data object for insertion - into the table. + generator must be a function taking arguments (row_number, + col_number) returning a suitable data object for insertion + into the table. """ self.table = self.new_table_name() diff --git a/tests/configdb.py b/tests/configdb.py index f3a56e24..c2949039 100644 --- a/tests/configdb.py +++ b/tests/configdb.py @@ -5,7 +5,10 @@ tests_path = path.dirname(__file__) conf_file = environ.get("TESTDB", "default.cnf") conf_path = path.join(tests_path, conf_file) -connect_kwargs = dict(read_default_file=conf_path, read_default_group="MySQLdb-tests",) +connect_kwargs = dict( + read_default_file=conf_path, + read_default_group="MySQLdb-tests", +) def connection_kwargs(kwargs): diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 0ca8bce6..4824d9cc 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -66,25 +66,25 @@ class DatabaseAPI20Test(unittest.TestCase): - """ Test a database self.driver for DB API 2.0 compatibility. - This implementation tests Gadfly, but the TestCase - is structured so that other self.drivers can subclass this - test case to ensure compiliance with the DB-API. It is - expected that this TestCase may be expanded in the future - if ambiguities or edge conditions are discovered. + """Test a database self.driver for DB API 2.0 compatibility. + This implementation tests Gadfly, but the TestCase + is structured so that other self.drivers can subclass this + test case to ensure compiliance with the DB-API. It is + expected that this TestCase may be expanded in the future + if ambiguities or edge conditions are discovered. - The 'Optional Extensions' are not yet being tested. + The 'Optional Extensions' are not yet being tested. - self.drivers should subclass this test, overriding setUp, tearDown, - self.driver, connect_args and connect_kw_args. Class specification - should be as follows: + self.drivers should subclass this test, overriding setUp, tearDown, + self.driver, connect_args and connect_kw_args. Class specification + should be as follows: - import dbapi20 - class mytest(dbapi20.DatabaseAPI20Test): - [...] + import dbapi20 + class mytest(dbapi20.DatabaseAPI20Test): + [...] - Don't 'import DatabaseAPI20Test from dbapi20', or you will - confuse the unit tester - just 'import dbapi20'. + Don't 'import DatabaseAPI20Test from dbapi20', or you will + confuse the unit tester - just 'import dbapi20'. """ # The self.driver module. This should be the module where the 'connect' @@ -110,15 +110,15 @@ def executeDDL2(self, cursor): cursor.execute(self.ddl2) def setUp(self): - """ self.drivers should override this method to perform required setup - if any is necessary, such as creating the database. + """self.drivers should override this method to perform required setup + if any is necessary, such as creating the database. """ pass def tearDown(self): - """ self.drivers should override this method to perform required cleanup - if any is necessary, such as deleting the test database. - The default drops the tables that may be created. + """self.drivers should override this method to perform required cleanup + if any is necessary, such as deleting the test database. + The default drops the tables that may be created. """ con = self._connect() try: @@ -521,8 +521,8 @@ def test_fetchone(self): ] def _populate(self): - """ Return a list of sql commands to setup the DB for the fetch - tests. + """Return a list of sql commands to setup the DB for the fetch + tests. """ populate = [ "insert into {}booze values ('{}')".format(self.table_prefix, s) @@ -710,9 +710,9 @@ def test_mixedfetch(self): con.close() def help_nextset_setUp(self, cur): - """ Should create a procedure called deleteme - that returns two result sets, first the - number of rows in booze then "name from booze" + """Should create a procedure called deleteme + that returns two result sets, first the + number of rows in booze then "name from booze" """ raise NotImplementedError("Helper not implemented") # sql=""" diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 6b3a3787..a0dd92a1 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -161,9 +161,10 @@ def test_callproc(self): pass # performed in test_MySQL_capabilities def help_nextset_setUp(self, cur): - """ Should create a procedure called deleteme - that returns two result sets, first the - number of rows in booze then "name from booze" + """ + Should create a procedure called deleteme + that returns two result sets, first the + number of rows in booze then "name from booze" """ sql = """ create procedure deleteme() From 0c25ad99d228af65e92dade18c1581e97a44e231 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 4 Dec 2020 13:51:46 +0900 Subject: [PATCH 249/396] Actions: Build MariaDB Connector/C with /MD --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 55a013b0..af694c94 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -36,7 +36,7 @@ jobs: run: | mkdir build cd build - cmake -A x64 .. -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static + cmake -A x64 .. -DCMAKE_C_FLAGS_RELEASE="/MD /O2 /Ob2 /DNDEBUG" -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static cmake --build . -j 8 --config Release cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake From 200b4a87924cf71b1e45d7336fd02ffc19fac1ac Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 4 Dec 2020 15:59:07 +0900 Subject: [PATCH 250/396] test --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index af694c94..55a013b0 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -36,7 +36,7 @@ jobs: run: | mkdir build cd build - cmake -A x64 .. -DCMAKE_C_FLAGS_RELEASE="/MD /O2 /Ob2 /DNDEBUG" -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static + cmake -A x64 .. -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static cmake --build . -j 8 --config Release cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake From 45ca1a2266345910605246988fde9a82f9443bbd Mon Sep 17 00:00:00 2001 From: SuperVirus Date: Mon, 7 Dec 2020 02:31:01 +0100 Subject: [PATCH 251/396] GitHub Actions workflow updated and caching fixed, Revert back to MariaDB Connector C 3.1.9 (#455) --- .github/workflows/windows.yaml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 55a013b0..abf9b9ea 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -2,8 +2,6 @@ name: Build windows wheels on: push: - branches: - - master create: workflow_dispatch: @@ -11,7 +9,7 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.1.11" + CONNECTOR_VERSION: "3.1.9" steps: - name: Cache Connector @@ -19,7 +17,7 @@ jobs: uses: actions/cache@v1 with: path: c:/mariadb-connector - key: mariadb-connector-${CONNECTOR_VERSION}-win + key: mariadb-connector-c-${{ env.CONNECTOR_VERSION }}-win - name: Download and Unzip Connector if: steps.cache-connector.outputs.cache-hit != 'true' @@ -41,15 +39,13 @@ jobs: cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake - name: Checkout mysqlclient - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: - ref: master - fetch-depth: 10 path: mysqlclient - name: Site Config shell: bash - working-directory: ../mysqlclient + working-directory: mysqlclient run: | pwd find . @@ -62,7 +58,7 @@ jobs: - name: Build wheels shell: cmd - working-directory: ../mysqlclient + working-directory: mysqlclient run: | py -3.9 -m pip install -U setuptools wheel pip py -3.9 setup.py bdist_wheel @@ -74,14 +70,14 @@ jobs: py -3.6 setup.py bdist_wheel - name: Upload Wheel - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: win-wheels - path: ../mysqlclient/dist + path: mysqlclient/dist/*.whl - name: Check wheels shell: bash - working-directory: ../mysqlclient/dist + working-directory: mysqlclient/dist run: | ls -la py -3.9 -m pip install --no-index --find-links . mysqlclient From 3c4a83bc52a46fec5c0f24bf3ce2a7e5b29c6e37 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 15:57:53 +0900 Subject: [PATCH 252/396] Don't use strncpy and strncat (#456) --- MySQLdb/_mysql.c | 64 +++++++++++++++++++++++++++++++----------------- setup_windows.py | 2 -- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 62d0969d..a58cf8f7 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1237,19 +1237,35 @@ _mysql_row_to_dict( c = PyTuple_GET_ITEM(self->converter, i); v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); if (!v) goto error; - if (!PyMapping_HasKeyString(r, fields[i].name)) { - PyMapping_SetItemString(r, fields[i].name, v); - } else { - int len; - char buf[256]; - strncpy(buf, fields[i].table, 256); - len = strlen(buf); - strncat(buf, ".", 256-len); - len = strlen(buf); - strncat(buf, fields[i].name, 256-len); - PyMapping_SetItemString(r, buf, v); + + PyObject *pyname = PyUnicode_FromString(fields[i].name); + if (pyname == NULL) { + Py_DECREF(v); + goto error; + } + + PyObject *tmp = PyDict_SetDefault(r, pyname, v); + Py_DECREF(pyname); + if (!tmp) { + Py_DECREF(v); + goto error; + } + if (tmp == v) { + Py_DECREF(v); + continue; + } + + pyname = PyUnicode_FromFormat("%s.%s", fields[i].table, fields[i].name); + if (!pyname) { + Py_DECREF(v); + goto error; } + int err = PyDict_SetItem(r, pyname, v); + Py_DECREF(pyname); Py_DECREF(v); + if (err) { + goto error; + } } return r; error: @@ -1275,20 +1291,22 @@ _mysql_row_to_dict_old( PyObject *v; c = PyTuple_GET_ITEM(self->converter, i); v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); - if (!v) goto error; - { - int len=0; - char buf[256]=""; - if (strlen(fields[i].table)) { - strncpy(buf, fields[i].table, 256); - len = strlen(buf); - strncat(buf, ".", 256-len); - len = strlen(buf); - } - strncat(buf, fields[i].name, 256-len); - PyMapping_SetItemString(r, buf, v); + if (!v) { + goto error; + } + + PyObject *pyname; + if (strlen(fields[i].table)) { + pyname = PyUnicode_FromFormat("%s.%s", fields[i].table, fields[i].name); + } else { + pyname = PyUnicode_FromString(fields[i].name); } + int err = PyDict_SetItem(r, pyname, v); + Py_DECREF(pyname); Py_DECREF(v); + if (err) { + goto error; + } } return r; error: diff --git a/setup_windows.py b/setup_windows.py index c374ad60..c25cc52b 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -38,7 +38,6 @@ def get_config(): libraries = ["kernel32", "advapi32", "wsock32", client] include_dirs = [os.path.join(connector, r"include")] - extra_compile_args = ["/Zl", "/D_CRT_SECURE_NO_WARNINGS"] extra_link_args = ["/MANIFEST"] name = "mysqlclient" @@ -53,7 +52,6 @@ def get_config(): ext_options = dict( library_dirs=library_dirs, libraries=libraries, - extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, include_dirs=include_dirs, extra_objects=extra_objects, From 641c989906303eacdcb49a6d27f0f48702743e57 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 11:58:45 +0900 Subject: [PATCH 253/396] MariaDB Connector/C 3.1.10 --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index abf9b9ea..5a2e09f6 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -9,7 +9,7 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.1.9" + CONNECTOR_VERSION: "3.1.10" steps: - name: Cache Connector From c89bf23433bee9e5091f1adb4d7db7a10dac922f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 12:00:23 +0900 Subject: [PATCH 254/396] don't run action on create --- .github/workflows/windows.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 5a2e09f6..11c5650e 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -2,7 +2,6 @@ name: Build windows wheels on: push: - create: workflow_dispatch: jobs: From b2d577fa0d2151a65907bf79c6a6c0f257745013 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 15:59:10 +0900 Subject: [PATCH 255/396] Actions: Update MariaDB C/C to 3.1.11. --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 11c5650e..a560d48f 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -8,7 +8,7 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.1.10" + CONNECTOR_VERSION: "3.1.11" steps: - name: Cache Connector From f3df03e4412de3f023d9a6ed0e12c9cfdc633739 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 16:24:10 +0900 Subject: [PATCH 256/396] Actions: Add lint workflow --- .github/workflows/lint.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..d6aff95a --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,17 @@ +name: Lint + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: psf/black@stable + - name: Setup flake8 annotations + uses: rbialon/flake8-annotations@v1 + - name: flake8 + run: | + pip install flake8 + flake8 --ignore=E203,E501,W503 --max-line-length=88 . From fb3e6a16f73ffe747776eb6610bfb5e86b7b00ee Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 16:29:04 +0900 Subject: [PATCH 257/396] black doc --- doc/conf.py | 78 ++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 33f9781c..5d8cd1a0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -18,12 +18,12 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath("..")) +# sys.path.insert(0, os.path.abspath("..")) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = "1.0" +# needs_sphinx = "1.0" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -36,7 +36,7 @@ source_suffix = ".rst" # The encoding of source files. -#source_encoding = "utf-8-sig" +# source_encoding = "utf-8-sig" # The master toctree document. master_doc = "index" @@ -56,37 +56,37 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = "" +# today = "" # Else, today_fmt is used as the format for a strftime call. -#today_fmt = "%B %d, %Y" +# today_fmt = "%B %d, %Y" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -98,26 +98,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -126,44 +126,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = "%b %d, %Y" +# html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = "" +# html_use_opensearch = "" # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "MySQLdbdoc" @@ -188,23 +188,23 @@ # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -214,7 +214,7 @@ man_pages = [("index", "mysqldb", "MySQLdb Documentation", ["Andy Dustman"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -235,10 +235,10 @@ ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' From 3431f56324ebed9a0ab048c84e61fc72b0ed0011 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 16:31:03 +0900 Subject: [PATCH 258/396] Travis: Remove flake8 and black. They run in Github Actions. --- .travis.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec1cd379..f77d5e01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,20 +60,6 @@ jobs: script: - cd django-${DJANGO_VERSION}/tests/ - ./runtests.py --parallel=2 --settings=test_mysql - - name: flake8 - python: "3.8" - install: - - pip install -U pip - - pip install flake8 - script: - - flake8 --ignore=E203,E501,W503 --max-line-length=88 . - - name: black - python: "3.8" - install: - - pip install -U pip - - pip install black - script: - - black --check --exclude=doc/ . #- &django_3_0 # <<: *django_2_2 # name: "Django 3.0 test (Python 3.8)" From 329bae79c88163e1550d7b05190cc4cf64e6292e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 7 Dec 2020 17:39:09 +0900 Subject: [PATCH 259/396] Run tests in Github Actions (#457) --- .github/workflows/tests.yaml | 36 ++++++++++++++++++++++++++++++ .travis.yml | 5 ----- tests/actions.cnf | 11 +++++++++ tests/test_MySQLdb_capabilities.py | 6 ++--- tests/travis.cnf | 1 - 5 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/tests.yaml create mode 100644 tests/actions.cnf diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..5b69b416 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,36 @@ +name: Test + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + services: + mysql: + image: mysql:8.0 + ports: + - 3306:3306 + env: + MYSQL_DATABASE: mysqldb_test + MYSQL_ROOT_PASSWORD: secretsecret + options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10 + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run tests + env: + TESTDB: actions.cnf + run: | + pip install -U pip + pip install -U mock coverage pytest pytest-cov + pip install . + pytest --cov ./MySQLdb + - uses: codecov/codecov-action@v1 diff --git a/.travis.yml b/.travis.yml index f77d5e01..75c6d425 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,6 @@ language: python python: - "nightly" - "pypy3" - - "3.9-dev" - - "3.8" - - "3.7" - - "3.6" - - "3.5" cache: pip diff --git a/tests/actions.cnf b/tests/actions.cnf new file mode 100644 index 00000000..8918f031 --- /dev/null +++ b/tests/actions.cnf @@ -0,0 +1,11 @@ +# To create your own custom version of this file, read +# http://dev.mysql.com/doc/refman/5.1/en/option-files.html +# and set TESTDB in your environment to the name of the file + +[MySQLdb-tests] +host = 127.0.0.1 +port = 3306 +user = root +database = mysqldb_test +password = secretsecret +default-character-set = utf8mb4 diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index fe9ef03e..0b4dd21a 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -120,12 +120,12 @@ def test_MULTIPOLYGON(self): INSERT INTO test_MULTIPOLYGON (id, border) VALUES (1, - Geomfromtext( + ST_Geomfromtext( 'MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))')) """ ) - c.execute("SELECT id, AsText(border) FROM test_MULTIPOLYGON") + c.execute("SELECT id, ST_AsText(border) FROM test_MULTIPOLYGON") row = c.fetchone() self.assertEqual(row[0], 1) self.assertEqual( @@ -133,7 +133,7 @@ def test_MULTIPOLYGON(self): "MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))", ) - c.execute("SELECT id, AsWKB(border) FROM test_MULTIPOLYGON") + c.execute("SELECT id, ST_AsWKB(border) FROM test_MULTIPOLYGON") row = c.fetchone() self.assertEqual(row[0], 1) self.assertNotEqual(len(row[1]), 0) diff --git a/tests/travis.cnf b/tests/travis.cnf index 05ff8039..5fd6f847 100644 --- a/tests/travis.cnf +++ b/tests/travis.cnf @@ -7,5 +7,4 @@ host = 127.0.0.1 port = 3306 user = root database = mysqldb_test -#password = travis default-character-set = utf8mb4 From 1731d273410909ef8efe1188d98e3fcd9d274144 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 8 Dec 2020 18:35:44 +0900 Subject: [PATCH 260/396] Fix and optimize fetching dict rows. (#458) --- .github/workflows/windows.yaml | 2 + MySQLdb/_mysql.c | 131 ++++++++++++++++++++++++--------- tests/test_cursor.py | 39 ++++++++++ 3 files changed, 136 insertions(+), 36 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index a560d48f..c65ce188 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -2,6 +2,8 @@ name: Build windows wheels on: push: + branches: + - master workflow_dispatch: jobs: diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index a58cf8f7..27880ca2 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1194,7 +1194,8 @@ _mysql_field_to_python( static PyObject * _mysql_row_to_tuple( _mysql_ResultObject *self, - MYSQL_ROW row) + MYSQL_ROW row, + PyObject *unused) { unsigned int n, i; unsigned long *length; @@ -1221,7 +1222,8 @@ _mysql_row_to_tuple( static PyObject * _mysql_row_to_dict( _mysql_ResultObject *self, - MYSQL_ROW row) + MYSQL_ROW row, + PyObject *cache) { unsigned int n, i; unsigned long *length; @@ -1243,40 +1245,42 @@ _mysql_row_to_dict( Py_DECREF(v); goto error; } - - PyObject *tmp = PyDict_SetDefault(r, pyname, v); - Py_DECREF(pyname); - if (!tmp) { + int err = PyDict_Contains(r, pyname); + if (err < 0) { // error Py_DECREF(v); goto error; } - if (tmp == v) { - Py_DECREF(v); - continue; + if (err) { // duplicate + Py_DECREF(pyname); + pyname = PyUnicode_FromFormat("%s.%s", fields[i].table, fields[i].name); + if (pyname == NULL) { + Py_DECREF(v); + goto error; + } } - pyname = PyUnicode_FromFormat("%s.%s", fields[i].table, fields[i].name); - if (!pyname) { - Py_DECREF(v); - goto error; + err = PyDict_SetItem(r, pyname, v); + if (cache) { + PyTuple_SET_ITEM(cache, i, pyname); + } else { + Py_DECREF(pyname); } - int err = PyDict_SetItem(r, pyname, v); - Py_DECREF(pyname); Py_DECREF(v); if (err) { goto error; } } return r; - error: - Py_XDECREF(r); +error: + Py_DECREF(r); return NULL; } static PyObject * _mysql_row_to_dict_old( _mysql_ResultObject *self, - MYSQL_ROW row) + MYSQL_ROW row, + PyObject *cache) { unsigned int n, i; unsigned long *length; @@ -1302,8 +1306,12 @@ _mysql_row_to_dict_old( pyname = PyUnicode_FromString(fields[i].name); } int err = PyDict_SetItem(r, pyname, v); - Py_DECREF(pyname); Py_DECREF(v); + if (cache) { + PyTuple_SET_ITEM(cache, i, pyname); + } else { + Py_DECREF(pyname); + } if (err) { goto error; } @@ -1314,15 +1322,66 @@ _mysql_row_to_dict_old( return NULL; } -typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); +static PyObject * +_mysql_row_to_dict_cached( + _mysql_ResultObject *self, + MYSQL_ROW row, + PyObject *cache) +{ + PyObject *r = PyDict_New(); + if (!r) { + return NULL; + } + + unsigned int n = mysql_num_fields(self->result); + unsigned long *length = mysql_fetch_lengths(self->result); + MYSQL_FIELD *fields = mysql_fetch_fields(self->result); + + for (unsigned int i=0; iconverter, i); + PyObject *v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); + if (!v) { + goto error; + } + + PyObject *pyname = PyTuple_GET_ITEM(cache, i); // borrowed + int err = PyDict_SetItem(r, pyname, v); + Py_DECREF(v); + if (err) { + goto error; + } + } + return r; + error: + Py_XDECREF(r); + return NULL; +} + + +typedef PyObject *_convertfunc(_mysql_ResultObject *, MYSQL_ROW, PyObject *); +static _convertfunc * const row_converters[] = { + _mysql_row_to_tuple, + _mysql_row_to_dict, + _mysql_row_to_dict_old +}; Py_ssize_t _mysql__fetch_row( _mysql_ResultObject *self, PyObject *r, /* list object */ Py_ssize_t maxrows, - _PYFUNC *convert_row) + int how) { + _convertfunc *convert_row = row_converters[how]; + + PyObject *cache = NULL; + if (maxrows > 0 && how > 0) { + cache = PyTuple_New(mysql_num_fields(self->result)); + if (!cache) { + return -1; + } + } + Py_ssize_t i; for (i = 0; i < maxrows; i++) { MYSQL_ROW row; @@ -1335,20 +1394,29 @@ _mysql__fetch_row( } if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { _mysql_Exception((_mysql_ConnectionObject *)self->conn); - return -1; + goto error; } if (!row) { break; } - PyObject *v = convert_row(self, row); - if (!v) return -1; + PyObject *v = convert_row(self, row, cache); + if (!v) { + goto error; + } + if (cache) { + convert_row = _mysql_row_to_dict_cached; + } if (PyList_Append(r, v)) { Py_DECREF(v); - return -1; + goto error; } Py_DECREF(v); } + Py_XDECREF(cache); return i; +error: + Py_XDECREF(cache); + return -1; } static char _mysql_ResultObject_fetch_row__doc__[] = @@ -1366,15 +1434,7 @@ _mysql_ResultObject_fetch_row( PyObject *args, PyObject *kwargs) { - typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); - static char *kwlist[] = { "maxrows", "how", NULL }; - static _PYFUNC *row_converters[] = - { - _mysql_row_to_tuple, - _mysql_row_to_dict, - _mysql_row_to_dict_old - }; - _PYFUNC *convert_row; + static char *kwlist[] = {"maxrows", "how", NULL }; int maxrows=1, how=0; PyObject *r=NULL; @@ -1386,7 +1446,6 @@ _mysql_ResultObject_fetch_row( PyErr_SetString(PyExc_ValueError, "how out of range"); return NULL; } - convert_row = row_converters[how]; if (!maxrows) { if (self->use) { maxrows = INT_MAX; @@ -1396,7 +1455,7 @@ _mysql_ResultObject_fetch_row( } } if (!(r = PyList_New(0))) goto error; - Py_ssize_t rowsadded = _mysql__fetch_row(self, r, maxrows, convert_row); + Py_ssize_t rowsadded = _mysql__fetch_row(self, r, maxrows, how); if (rowsadded == -1) goto error; /* DB-API allows return rows as list. diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 479f3e27..91f0323e 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -111,3 +111,42 @@ def test_pyparam(): assert cursor._executed == b"SELECT 1, 2" cursor.execute(b"SELECT %(a)s, %(b)s", {b"a": 3, b"b": 4}) assert cursor._executed == b"SELECT 3, 4" + + +def test_dictcursor(): + conn = connect() + cursor = conn.cursor(MySQLdb.cursors.DictCursor) + + cursor.execute("CREATE TABLE t1 (a int, b int, c int)") + _tables.append("t1") + cursor.execute("INSERT INTO t1 (a,b,c) VALUES (1,1,47), (2,2,47)") + + cursor.execute("CREATE TABLE t2 (b int, c int)") + _tables.append("t2") + cursor.execute("INSERT INTO t2 (b,c) VALUES (1,1), (2,2)") + + cursor.execute("SELECT * FROM t1 JOIN t2 ON t1.b=t2.b") + rows = cursor.fetchall() + + assert len(rows) == 2 + assert rows[0] == {"a": 1, "b": 1, "c": 47, "t2.b": 1, "t2.c": 1} + assert rows[1] == {"a": 2, "b": 2, "c": 47, "t2.b": 2, "t2.c": 2} + + names1 = sorted(rows[0]) + names2 = sorted(rows[1]) + for a, b in zip(names1, names2): + assert a is b + + # Old fetchtype + cursor._fetch_type = 2 + cursor.execute("SELECT * FROM t1 JOIN t2 ON t1.b=t2.b") + rows = cursor.fetchall() + + assert len(rows) == 2 + assert rows[0] == {"t1.a": 1, "t1.b": 1, "t1.c": 47, "t2.b": 1, "t2.c": 1} + assert rows[1] == {"t1.a": 2, "t1.b": 2, "t1.c": 47, "t2.b": 2, "t2.c": 2} + + names1 = sorted(rows[0]) + names2 = sorted(rows[1]) + for a, b in zip(names1, names2): + assert a is b From 5385f5187afae1b714d21d45654474fd9894e4a5 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 10 Dec 2020 12:41:32 +0900 Subject: [PATCH 261/396] v2.0.2 --- HISTORY.rst | 9 +++++++++ metadata.cfg | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 778b6431..6ad6e148 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,12 @@ +====================== + What's new in 2.0.2 +====================== + +Release: 2020-12-10 + +* Windows: Update MariaDB Connector/C to 3.1.11. +* Optimize fetching many rows with DictCursor. + ====================== What's new in 2.0.1 ====================== diff --git a/metadata.cfg b/metadata.cfg index 527a2c7b..b0ee0db4 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,12 +1,12 @@ [metadata] -version: 2.0.1 -version_info: (2,0,1,'final',0) +version: 2.0.2 +version_info: (2,0,2,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com license: GPL platforms: ALL -url: https://github.com/PyMySQL/mysqlclient-python +url: https://github.com/PyMySQL/mysqlclient classifiers: Development Status :: 5 - Production/Stable Environment :: Other Environment @@ -20,10 +20,10 @@ classifiers: Programming Language :: C Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: From 401ca8297439d8e34fff0ebda19bf6121de5d2ed Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 11 Dec 2020 12:30:08 +0900 Subject: [PATCH 262/396] Actions: Use installed mysql instead of docker. (#462) --- .github/workflows/tests.yaml | 13 ++++--------- tests/actions.cnf | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5b69b416..748d2c2b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -10,16 +10,11 @@ jobs: strategy: matrix: python-version: [3.5, 3.6, 3.7, 3.8, 3.9] - services: - mysql: - image: mysql:8.0 - ports: - - 3306:3306 - env: - MYSQL_DATABASE: mysqldb_test - MYSQL_ROOT_PASSWORD: secretsecret - options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10 steps: + - name: Start MySQL + run: | + sudo systemctl start mysql.service + mysql -uroot -proot -e "CREATE DATABASE mysqldb_test" - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 diff --git a/tests/actions.cnf b/tests/actions.cnf index 8918f031..d20296d6 100644 --- a/tests/actions.cnf +++ b/tests/actions.cnf @@ -7,5 +7,5 @@ host = 127.0.0.1 port = 3306 user = root database = mysqldb_test -password = secretsecret +password = root default-character-set = utf8mb4 From efa7053a3eb0b3ac14505e32bf91371c7f5f43f6 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 1 Jan 2021 10:24:41 +0900 Subject: [PATCH 263/396] Improve cflags and ldflags (#468) * Add "-std=c99" to cflags implicitly * And Support MYSQLCLIDNT_CFLAGS and MYSQLCLIENT_LDFLAGS --- setup_posix.py | 63 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/setup_posix.py b/setup_posix.py index 5602be84..e556f5c5 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -19,9 +19,9 @@ def dequote(s): def mysql_config(what): - from os import popen - - f = popen("{} --{}".format(_mysql_config_path, what)) + cmd = "{} --{}".format(_mysql_config_path, what) + print(cmd) + f = os.popen(cmd) data = f.read().strip().split() ret = f.close() if ret: @@ -29,6 +29,7 @@ def mysql_config(what): data = [] if ret / 256 > 1: raise OSError("{} not found".format(_mysql_config_path)) + print(data) return data @@ -62,26 +63,41 @@ def get_config(): static = True sys.argv.remove("--static") - libs = mysql_config("libs") + libs = os.environ.get("MYSQLCLIENT_LDFLAGS") + if libs: + libs = libs.strip().split() + else: + libs = mysql_config("libs") library_dirs = [dequote(i[2:]) for i in libs if i.startswith("-L")] libraries = [dequote(i[2:]) for i in libs if i.startswith("-l")] extra_link_args = [x for x in libs if not x.startswith(("-l", "-L"))] - removable_compile_args = ("-I", "-L", "-l") - extra_compile_args = [ - i.replace("%", "%%") - for i in mysql_config("cflags") - if i[:2] not in removable_compile_args - ] + cflags = os.environ.get("MYSQLCLIENT_CFLAGS") + if cflags: + use_mysqlconfig_cflags = False + cflags = cflags.strip().split() + else: + use_mysqlconfig_cflags = True + cflags = mysql_config("cflags") + + include_dirs = [] + extra_compile_args = ["-std=c99"] + + for a in cflags: + if a.startswith("-I"): + include_dirs.append(dequote(a[2:])) + elif a.startswith(("-L", "-l")): # This should be LIBS. + pass + else: + extra_compile_args.append(a.replace("%", "%%")) # Copy the arch flags for linking as well - for i in range(len(extra_compile_args)): - if extra_compile_args[i] == "-arch": + try: + i = extra_compile_args.index("-arch") + if "-arch" not in extra_link_args: extra_link_args += ["-arch", extra_compile_args[i + 1]] - - include_dirs = [ - dequote(i[2:]) for i in mysql_config("include") if i.startswith("-I") - ] + except ValueError: + pass if static: # properly handle mysql client libraries that are not called libmysqlclient @@ -109,11 +125,12 @@ def get_config(): if client in libraries: libraries.remove(client) else: - # mysql_config may have "-lmysqlclient -lz -lssl -lcrypto", but zlib and - # ssl is not used by _mysql. They are needed only for static build. - for L in ("crypto", "ssl", "z"): - if L in libraries: - libraries.remove(L) + if use_mysqlconfig_cflags: + # mysql_config may have "-lmysqlclient -lz -lssl -lcrypto", but zlib and + # ssl is not used by _mysql. They are needed only for static build. + for L in ("crypto", "ssl", "z"): + if L in libraries: + libraries.remove(L) name = "mysqlclient" metadata["name"] = name @@ -138,6 +155,10 @@ def get_config(): if static: ext_options["language"] = "c++" + print("ext_options:") + for k, v in ext_options.items(): + print(" {}: {}".format(k, v)) + return metadata, ext_options From fdace640af1e469e05cd68ed9821d45341fb3b03 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 1 Jan 2021 20:42:01 +0900 Subject: [PATCH 264/396] Update README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 79af2573..f65329cf 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,20 @@ Then you can install mysqlclient via pip now: $ pip install mysqlclient ``` +### Customize build (POSIX) + +mysqlclient uses `mysql_config` or `mariadb_config` by default for finding +compiler/linker flags. + +You can use `MYSQLCLIENT_CFLAGS` and `MYSQLCLIENT_LDFLAGS` environment +variables to customize compiler/linker options. + +``` +$ export MYSQLCLIENT_CFLAGS=`pkg-config mysqlclient --cflags` +$ export MYSQLCLIENT_LDFLAGS=`pkg-config mysqlclient --libs` +$ pip install mysqlclient +``` + ### Documentation Documentation is hosted on [Read The Docs](https://mysqlclient.readthedocs.io/) From ceb15581caeb7c0ec9c656cb2d98d8c65fbb9335 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 1 Jan 2021 20:46:52 +0900 Subject: [PATCH 265/396] v2.0.3 --- HISTORY.rst | 11 +++++++++++ metadata.cfg | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 6ad6e148..16fcbd59 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,14 @@ +====================== + What's new in 2.0.3 +====================== + +Release: 2021-01-01 + +* Add ``-std=c99`` option to cflags by default for ancient compilers that doesn't + accept C99 by default. +* You can customize cflags and ldflags by setting ``MYSQLCLIENT_CFLAGS`` and + ``MYSQLCLIENT_LDFLAGS``. It overrides ``mysql_config``. + ====================== What's new in 2.0.2 ====================== diff --git a/metadata.cfg b/metadata.cfg index b0ee0db4..2a2e64fa 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.0.2 -version_info: (2,0,2,'final',0) +version: 2.0.3 +version_info: (2,0,3,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From fa25358d0f171bd8a63729c5a8d76528f4ae74e9 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 14:39:43 +0900 Subject: [PATCH 266/396] deprecate passwd and db --- MySQLdb/connections.py | 4 ++-- doc/user_guide.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 8e226ffe..eca51ed5 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -62,9 +62,9 @@ def __init__(self, *args, **kwargs): :param str host: host to connect :param str user: user to connect as :param str password: password to use - :param str passwd: alias of password, for backward compatibility + :param str passwd: alias of password (deprecated) :param str database: database to use - :param str db: alias of database, for backward compatibility + :param str db: alias of database (deprecated) :param int port: TCP/IP port to connect to :param str unix_socket: location of unix_socket to use :param dict conv: conversion dictionary, see MySQLdb.converters diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 83b800e8..decec78f 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -277,10 +277,10 @@ connect(parameters...) user user to authenticate as. Default: current effective user. - passwd + password password to authenticate with. Default: no password. - db + database database to use. Default: no default database. port From df0bc7d8e94e3d56150d05c22e5dd1d8840640b4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 15:39:21 +0900 Subject: [PATCH 267/396] Remove INSTALL.rst It is not maintenanced. --- INSTALL.rst | 146 ---------------------------------------------------- MANIFEST.in | 1 - Makefile | 3 +- 3 files changed, 1 insertion(+), 149 deletions(-) delete mode 100644 INSTALL.rst diff --git a/INSTALL.rst b/INSTALL.rst deleted file mode 100644 index 0b49f3e6..00000000 --- a/INSTALL.rst +++ /dev/null @@ -1,146 +0,0 @@ -==================== -MySQLdb Installation -==================== - -.. contents:: -.. - -Prerequisites -------------- - -+ Python 3.5 or higher - -+ setuptools - - * https://pypi.org/project/setuptools/ - -+ MySQL 5.5 or higher - - * https://www.mysql.com/downloads/ - - * MySQL 5.1 may work, but not supported. - -+ C compiler - - * Most free software-based systems already have this, usually gcc. - - * Most commercial UNIX platforms also come with a C compiler, or - you can also use gcc. - - * If you have some Windows flavor, you should use Windows SDK or - Visual C++. - - -Building and installing ------------------------ - -The setup.py script uses mysql_config to find all compiler and linker -options, and should work as is on any POSIX-like platform, so long as -mysql_config is in your path. - -Depending on which version of MySQL you have, you may have the option -of using three different client libraries. To select the client library, -edit the [options] section of site.cfg: - - static - if True, try to link against a static library; otherwise link - against dynamic libraries (default). - This option doesn't work for MySQL>5.6 since libmysqlclient - requires libstdc++. If you want to use, add `-lstdc++` to - mysql_config manually. - -If `/lib` is not added to `/etc/ld.so.conf`, `import _mysql` -doesn't work. To fix this, (1) set `LD_LIBRARY_PATH`, or (2) add -`-Wl,-rpath,/lib` to ldflags in your mysql_config. - -Finally, putting it together:: - - $ tar xz mysqlclient-1.3.6.tar.gz - $ cd mysqlclient-1.3.6 - $ # edit site.cfg if necessary - $ python setup.py build - $ sudo python setup.py install # or su first - - -Windows -....... - -I don't do Windows. However if someone provides me with a package for -Windows, I'll make it available. Don't ask me for help with Windows -because I can't help you. - -Generally, though, running setup.py is similar to above:: - - C:\...> python setup.py install - C:\...> python setup.py bdist_wininst - -The latter example should build a Windows installer package, if you -have the correct tools. In any event, you *must* have a C compiler. -Additionally, you have to set an environment variable (mysqlroot) -which is the path to your MySQL installation. In theory, it would be -possible to get this information out of the registry, but like I said, -I don't do Windows, but I'll accept a patch that does this. - -On Windows, you will definitely have to edit site.cfg since there is -no mysql_config in the MySQL package. - - -Binary Packages ---------------- - -I don't plan to make binary packages any more. However, if someone -contributes one, I will make it available. Several OS vendors have -their own packages available. - - -Red Hat Linux -............. - -MySQL-python is pre-packaged in Red Hat Linux 7.x and newer. This -includes Fedora Core and Red Hat Enterprise Linux. - - -Debian GNU/Linux -................ - -Packaged as `python-mysqldb`_:: - - # apt-get install python-mysqldb - -Or use Synaptic. - -.. _`python-mysqldb`: http://packages.debian.org/python-mysqldb - - -Ubuntu -...... - -Same as with Debian. - - -Gentoo Linux -............ - -Packaged as `mysql-python`_. :: - - # emerge sync - # emerge mysql-python - # emerge zmysqlda # if you use Zope - -.. _`mysql-python`: https://packages.gentoo.org/packages/search?q=mysql-python - - -BSD -... - -MySQL-python is a ported package in FreeBSD, NetBSD, and OpenBSD, -although the name may vary to match OS conventions. - - -License -------- - -GPL or the original license based on Python 1.5.2's license. - - -:Author: Andy Dustman diff --git a/MANIFEST.in b/MANIFEST.in index 415a995a..07563caf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,6 @@ recursive-include tests *.py include doc/conf.py include MANIFEST.in include HISTORY.rst -include INSTALL.rst include README.md include LICENSE include metadata.cfg diff --git a/Makefile b/Makefile index 8c8cddc8..a3d65492 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ doc: .PHONY: clean clean: + python3 setup.py clean find . -name '*.pyc' -delete find . -name '__pycache__' -delete - rm *.so - python3 setup.py clean From 3129b24328adbae873005d76c7c822e534d841dc Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 15:48:05 +0900 Subject: [PATCH 268/396] Update Makefile. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a3d65492..783d1919 100644 --- a/Makefile +++ b/Makefile @@ -13,3 +13,4 @@ clean: python3 setup.py clean find . -name '*.pyc' -delete find . -name '__pycache__' -delete + rm -rf build From 3e95f351d4b90590afe69f90d84382111a04f020 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 15:49:11 +0900 Subject: [PATCH 269/396] Move codecov.yml --- MySQLdb/codecov.yml => codecov.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MySQLdb/codecov.yml => codecov.yml (100%) diff --git a/MySQLdb/codecov.yml b/codecov.yml similarity index 100% rename from MySQLdb/codecov.yml rename to codecov.yml From 729ec54e39b868dd6970ee9779fed8354657d9a4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 16:03:50 +0900 Subject: [PATCH 270/396] Update README --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f65329cf..79617fd0 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # mysqlclient -[![Build Status](https://secure.travis-ci.org/PyMySQL/mysqlclient-python.png)](http://travis-ci.org/PyMySQL/mysqlclient-python) +This project is a fork of [MySQLdb1](https://github.com/farcepest/MySQLdb1). +This project adds Python 3 support and fixed many bugs. -This is a fork of [MySQLdb1](https://github.com/farcepest/MySQLdb1). - -This project adds Python 3 support and bug fixes. -I hope this fork is merged back to MySQLdb1 like distribute was merged back to setuptools. +* PyPI: https://pypi.org/project/mysqlclient/ +* GitHub: https://github.com/PyMySQL/mysqlclient ## Support @@ -83,4 +82,3 @@ $ pip install mysqlclient ### Documentation Documentation is hosted on [Read The Docs](https://mysqlclient.readthedocs.io/) - From af563dbf5186b406190d1337beea99f9f6f05cb9 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 16:28:06 +0900 Subject: [PATCH 271/396] Use unittest.mock instead of mock (#469) --- .github/workflows/tests.yaml | 2 +- tests/test_MySQLdb_times.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 748d2c2b..2544b397 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,7 +25,7 @@ jobs: TESTDB: actions.cnf run: | pip install -U pip - pip install -U mock coverage pytest pytest-cov + pip install -U coverage pytest pytest-cov pip install . pytest --cov ./MySQLdb - uses: codecov/codecov-action@v1 diff --git a/tests/test_MySQLdb_times.py b/tests/test_MySQLdb_times.py index 0947f3e5..2081b1ac 100644 --- a/tests/test_MySQLdb_times.py +++ b/tests/test_MySQLdb_times.py @@ -1,11 +1,11 @@ -import mock -import unittest -from time import gmtime from datetime import time, date, datetime, timedelta +from time import gmtime +import unittest +from unittest import mock +import warnings from MySQLdb import times -import warnings warnings.simplefilter("ignore") From 52c2a12861b5bb3cd53ac5438b5905affe254a97 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 16:29:52 +0900 Subject: [PATCH 272/396] Actions: Use fetch-depth:2 for codecov --- .github/workflows/tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2544b397..359ac575 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -16,6 +16,8 @@ jobs: sudo systemctl start mysql.service mysql -uroot -proot -e "CREATE DATABASE mysqldb_test" - uses: actions/checkout@v2 + with: + fetch-depth: 2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: From 67eb9c51346408049010f65bfb646978ebacc512 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 16:43:36 +0900 Subject: [PATCH 273/396] Actions: Fix pytest args (#470) --- .github/workflows/tests.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 359ac575..6f4bea3b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -26,8 +26,7 @@ jobs: env: TESTDB: actions.cnf run: | - pip install -U pip pip install -U coverage pytest pytest-cov pip install . - pytest --cov ./MySQLdb + pytest --cov=MySQLdb tests - uses: codecov/codecov-action@v1 From ca3ed6ae6745a13365cd849a801ba61f076835ee Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 16:45:47 +0900 Subject: [PATCH 274/396] Actions: Use cache --- .github/workflows/tests.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6f4bea3b..ea0b29b3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -15,13 +15,23 @@ jobs: run: | sudo systemctl start mysql.service mysql -uroot -proot -e "CREATE DATABASE mysqldb_test" + + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-1 + restore-keys: | + ${{ runner.os }}-pip- + - uses: actions/checkout@v2 with: fetch-depth: 2 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Run tests env: TESTDB: actions.cnf @@ -29,4 +39,5 @@ jobs: pip install -U coverage pytest pytest-cov pip install . pytest --cov=MySQLdb tests + - uses: codecov/codecov-action@v1 From 44eace06defd6c192dc5be124d275f1b9268735a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 17:31:17 +0900 Subject: [PATCH 275/396] Actions: Fix measuring coverage (#471) --- .github/workflows/tests.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ea0b29b3..5d45b6a5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -32,12 +32,18 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Install dependencies + env: + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_DISABLE_PIP_VERSION_CHECK: 1 + run: | + pip install -U coverage pytest pytest-cov + python setup.py develop + - name: Run tests env: TESTDB: actions.cnf run: | - pip install -U coverage pytest pytest-cov - pip install . pytest --cov=MySQLdb tests - uses: codecov/codecov-action@v1 From 24aaa72c1503fe35cb1d0584ad553b41d10ddb48 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Jan 2021 17:39:31 +0900 Subject: [PATCH 276/396] cleanup --- MySQLdb/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 824acace..f56d8101 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -54,11 +54,6 @@ TimestampFromTicks, ) -try: - frozenset -except NameError: - from sets import ImmutableSet as frozenset - threadsafety = 1 apilevel = "2.0" paramstyle = "format" From 8f1fd73dd365ebf2e1785e77c8045f3e6abe3529 Mon Sep 17 00:00:00 2001 From: Simon Lundmark Date: Fri, 3 Sep 2021 04:01:58 +0200 Subject: [PATCH 277/396] multi statements can be disabled (#500) --- MySQLdb/connections.py | 13 +++++++++++-- tests/test_connection.py | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/test_connection.py diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index eca51ed5..7adc8835 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -110,6 +110,10 @@ class object, used to create cursors (keyword only) :param int client_flag: flags to use or 0 (see MySQL docs or constants/CLIENTS.py) + :param bool multi_statements: + If True, enable multi statements for clients >= 4.1. + Defaults to True. + :param str ssl_mode: specify the security settings for connection to the server; see the MySQL documentation for more details @@ -169,11 +173,16 @@ class object, used to create cursors (keyword only) self._binary_prefix = kwargs2.pop("binary_prefix", False) client_flag = kwargs.get("client_flag", 0) + client_version = tuple( [numeric_part(n) for n in _mysql.get_client_info().split(".")[:2]] ) - if client_version >= (4, 1): - client_flag |= CLIENT.MULTI_STATEMENTS + + multi_statements = kwargs2.pop("multi_statements", True) + if multi_statements: + if client_version >= (4, 1): + client_flag |= CLIENT.MULTI_STATEMENTS + if client_version >= (5, 0): client_flag |= CLIENT.MULTI_RESULTS diff --git a/tests/test_connection.py b/tests/test_connection.py new file mode 100644 index 00000000..960de572 --- /dev/null +++ b/tests/test_connection.py @@ -0,0 +1,26 @@ +import pytest + +from MySQLdb._exceptions import ProgrammingError + +from configdb import connection_factory + + +def test_multi_statements_default_true(): + conn = connection_factory() + cursor = conn.cursor() + + cursor.execute("select 17; select 2") + rows = cursor.fetchall() + assert rows == ((17,),) + + +def test_multi_statements_false(): + conn = connection_factory(multi_statements=False) + cursor = conn.cursor() + + with pytest.raises(ProgrammingError): + cursor.execute("select 17; select 2") + + cursor.execute("select 17") + rows = cursor.fetchall() + assert rows == ((17,),) From 355520dae017daf74505702a842443ff297dad06 Mon Sep 17 00:00:00 2001 From: Ryan Siemens Date: Thu, 2 Sep 2021 19:02:27 -0700 Subject: [PATCH 278/396] Remove bytes encoder that was specifically for Django 1.11 (#490) --- MySQLdb/connections.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 7adc8835..31f131db 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -195,13 +195,6 @@ class object, used to create cursors (keyword only) self.cursorclass = cursorclass self.encoders = {k: v for k, v in conv.items() if type(k) is not int} - # XXX THIS IS GARBAGE: While this is just a garbage and undocumented, - # Django 1.11 depends on it. And they don't fix it because - # they are in security-only fix mode. - # So keep this garbage for now. This will be removed in 1.5. - # See PyMySQL/mysqlclient-python#306 - self.encoders[bytes] = bytes - self._server_version = tuple( [numeric_part(n) for n in self.get_server_info().split(".")[:2]] ) From 62ddd30bb2b2585434d3f20c19e4869307faf8a1 Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Fri, 3 Sep 2021 04:02:49 +0200 Subject: [PATCH 279/396] update remnants of passwd and db also in docs (#488) --- doc/user_guide.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index decec78f..a00f292c 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -125,19 +125,19 @@ We haven't even begun to touch upon all the parameters ``connect()`` can take. For this reason, I prefer to use keyword parameters:: db=_mysql.connect(host="localhost",user="joebob", - passwd="moonpie",db="thangs") + password="moonpie",database="thangs") This does exactly what the last example did, but is arguably easier to read. But since the default host is "localhost", and if your login name really was "joebob", you could shorten it to this:: - db=_mysql.connect(passwd="moonpie",db="thangs") + db=_mysql.connect(password="moonpie",database="thangs") UNIX sockets and named pipes don't work over a network, so if you specify a host other than localhost, TCP will be used, and you can specify an odd port if you need to (the default port is 3306):: - db=_mysql.connect(host="outhouse",port=3307,passwd="moonpie",db="thangs") + db=_mysql.connect(host="outhouse",port=3307,password="moonpie",database="thangs") If you really had to, you could connect to the local host with TCP by specifying the full host name, or 127.0.0.1. @@ -145,7 +145,7 @@ specifying the full host name, or 127.0.0.1. Generally speaking, putting passwords in your code is not such a good idea:: - db=_mysql.connect(host="outhouse",db="thangs",read_default_file="~/.my.cnf") + db=_mysql.connect(host="outhouse",database="thangs",read_default_file="~/.my.cnf") This does what the previous example does, but gets the username and password and other parameters from ~/.my.cnf (UNIX-like systems). Read @@ -561,7 +561,7 @@ Some examples The ``connect()`` method works nearly the same as with `MySQLDB._mysql`_:: import MySQLdb - db=MySQLdb.connect(passwd="moonpie",db="thangs") + db=MySQLdb.connect(password="moonpie",database="thangs") To perform a query, you first need a cursor, and then you can execute queries on it:: From 180c9df4ab8325b75f6b26070951fd852ec76614 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Sep 2021 11:05:26 +0900 Subject: [PATCH 280/396] Remove -lzstd for non-static build --- setup_posix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup_posix.py b/setup_posix.py index e556f5c5..99763cbc 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -128,7 +128,7 @@ def get_config(): if use_mysqlconfig_cflags: # mysql_config may have "-lmysqlclient -lz -lssl -lcrypto", but zlib and # ssl is not used by _mysql. They are needed only for static build. - for L in ("crypto", "ssl", "z"): + for L in ("crypto", "ssl", "z", "zstd"): if L in libraries: libraries.remove(L) From 4bb85b25775b9192923fd0c19b07db61a2e4002b Mon Sep 17 00:00:00 2001 From: Ben Buchwald Date: Thu, 2 Sep 2021 22:10:11 -0400 Subject: [PATCH 281/396] Better support for building on Windows (#484) --- README.md | 16 +++++++++++++++ setup_windows.py | 52 +++++++++++++++++++++++------------------------- site.cfg | 2 +- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 79617fd0..4dbc54d7 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,22 @@ Or when you have question about MySQL: Building mysqlclient on Windows is very hard. But there are some binary wheels you can install easily. +If binary wheels do not exist for your version of Python, it may be possible to +build from source, but if this does not work, **do not come asking for support.** +To build from source, download the +[MariaDB C Connector](https://mariadb.com/downloads/#connectors) and install +it. It must be installed in the default location +(usually "C:\Program Files\MariaDB\MariaDB Connector C" or +"C:\Program Files (x86)\MariaDB\MariaDB Connector C" for 32-bit). If you +build the connector yourself or install it in a different location, set the +environment variable `MYSQLCLIENT_CONNECTOR` before installing. Once you have +the connector installed and an appropriate version of Visual Studio for your +version of Python: + +``` +$ pip install mysqlclient +``` + ### macOS (Homebrew) Install MySQL and mysqlclient: diff --git a/setup_windows.py b/setup_windows.py index c25cc52b..b2feb7d2 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -1,6 +1,5 @@ import os import sys -from distutils.msvccompiler import get_build_version def get_config(): @@ -8,35 +7,34 @@ def get_config(): metadata, options = get_metadata_and_options() - connector = options["connector"] + client = "mariadbclient" + connector = os.environ.get("MYSQLCLIENT_CONNECTOR", options.get("connector")) + if not connector: + connector = os.path.join( + os.environ["ProgramFiles"], "MariaDB", "MariaDB Connector C" + ) extra_objects = [] - # client = "mysqlclient" - client = "mariadbclient" - - vcversion = int(get_build_version()) - if client == "mariadbclient": - library_dirs = [os.path.join(connector, "lib", "mariadb")] - libraries = [ - "kernel32", - "advapi32", - "wsock32", - "shlwapi", - "Ws2_32", - "crypt32", - "secur32", - "bcrypt", - client, - ] - include_dirs = [os.path.join(connector, "include", "mariadb")] - else: - library_dirs = [ - os.path.join(connector, r"lib\vs%d" % vcversion), - os.path.join(connector, "lib"), - ] - libraries = ["kernel32", "advapi32", "wsock32", client] - include_dirs = [os.path.join(connector, r"include")] + library_dirs = [ + os.path.join(connector, "lib", "mariadb"), + os.path.join(connector, "lib"), + ] + libraries = [ + "kernel32", + "advapi32", + "wsock32", + "shlwapi", + "Ws2_32", + "crypt32", + "secur32", + "bcrypt", + client, + ] + include_dirs = [ + os.path.join(connector, "include", "mariadb"), + os.path.join(connector, "include"), + ] extra_link_args = ["/MANIFEST"] diff --git a/site.cfg b/site.cfg index 6b4596a4..08a14b0e 100644 --- a/site.cfg +++ b/site.cfg @@ -9,4 +9,4 @@ static = False # http://stackoverflow.com/questions/1972259/mysql-python-install-problem-using-virtualenv-windows-pip # Windows connector libs for MySQL. You need a 32-bit connector for your 32-bit Python build. -connector = C:\Program Files (x86)\MySQL\MySQL Connector C 6.1 +connector = From aa28721d073de9d023a172904a7de4f032476219 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Sep 2021 11:30:40 +0900 Subject: [PATCH 282/396] Use utf8mb4 in test_binary_prefix (#501) --- tests/test_MySQLdb_capabilities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 0b4dd21a..fc213b84 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -182,7 +182,7 @@ def test_binary_prefix(self): for binary_prefix in (True, False, None): kwargs = self.connect_kwargs.copy() # needs to be set to can guarantee CHARSET response for normal strings - kwargs["charset"] = "utf8" + kwargs["charset"] = "utf8mb4" if binary_prefix is not None: kwargs["binary_prefix"] = binary_prefix @@ -190,11 +190,11 @@ def test_binary_prefix(self): with closing(conn.cursor()) as c: c.execute("SELECT CHARSET(%s)", (MySQLdb.Binary(b"raw bytes"),)) self.assertEqual( - c.fetchall()[0][0], "binary" if binary_prefix else "utf8" + c.fetchall()[0][0], "binary" if binary_prefix else "utf8mb4" ) # normal strings should not get prefix c.execute("SELECT CHARSET(%s)", ("str",)) - self.assertEqual(c.fetchall()[0][0], "utf8") + self.assertEqual(c.fetchall()[0][0], "utf8mb4") if __name__ == "__main__": From 3ee07a031f06f660586765f5484d30953a69fe70 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 12:56:59 +0900 Subject: [PATCH 283/396] Windows: Update MariaDB Connector to 3.2.4 (#508) --- .github/workflows/windows.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index c65ce188..ac9b28da 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -10,7 +10,7 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.1.11" + CONNECTOR_VERSION: "3.2.4" steps: - name: Cache Connector @@ -61,14 +61,14 @@ jobs: shell: cmd working-directory: mysqlclient run: | + py -3.10 -m pip install -U setuptools wheel pip + py -3.10 setup.py bdist_wheel py -3.9 -m pip install -U setuptools wheel pip py -3.9 setup.py bdist_wheel py -3.8 -m pip install -U setuptools wheel pip py -3.8 setup.py bdist_wheel py -3.7 -m pip install -U setuptools wheel pip py -3.7 setup.py bdist_wheel - py -3.6 -m pip install -U setuptools wheel pip - py -3.6 setup.py bdist_wheel - name: Upload Wheel uses: actions/upload-artifact@v2 @@ -81,12 +81,12 @@ jobs: working-directory: mysqlclient/dist run: | ls -la + py -3.10 -m pip install --no-index --find-links . mysqlclient + py -3.10 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.9 -m pip install --no-index --find-links . mysqlclient py -3.9 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.8 -m pip install --no-index --find-links . mysqlclient py -3.8 -c "import MySQLdb; print(MySQLdb.version_info)" py -3.7 -m pip install --no-index --find-links . mysqlclient py -3.7 -c "import MySQLdb; print(MySQLdb.version_info)" - py -3.6 -m pip install --no-index --find-links . mysqlclient - py -3.6 -c "import MySQLdb; print(MySQLdb.version_info)" From dee7d882717ebccf5a468719254107a3c67561dd Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 13:11:16 +0900 Subject: [PATCH 284/396] set_character_set() sends "SET NAMES" always. (#509) Fix: #504. --- MySQLdb/connections.py | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 31f131db..482b12f0 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -200,10 +200,6 @@ class object, used to create cursors (keyword only) ) self.encoding = "ascii" # overridden in set_character_set() - db = proxy(self) - - def unicode_literal(u, dummy=None): - return db.string_literal(u.encode(db.encoding)) if not charset: charset = self.character_set_name() @@ -227,7 +223,13 @@ def unicode_literal(u, dummy=None): # MySQL may return JSON with charset==binary. self.converter[FIELD_TYPE.JSON] = str + db = proxy(self) + + def unicode_literal(u, dummy=None): + return db.string_literal(u.encode(db.encoding)) + self.encoders[str] = unicode_literal + self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: if autocommit is not None: @@ -300,32 +302,10 @@ def begin(self): """ self.query(b"BEGIN") - if not hasattr(_mysql.connection, "warning_count"): - - def warning_count(self): - """Return the number of warnings generated from the - last query. This is derived from the info() method.""" - info = self.info() - if info: - return int(info.split()[-1]) - else: - return 0 - def set_character_set(self, charset): - """Set the connection character set to charset. The character - set can only be changed in MySQL-4.1 and newer. If you try - to change the character set from the current value in an - older version, NotSupportedError will be raised.""" - py_charset = _charset_to_encoding.get(charset, charset) - if self.character_set_name() != charset: - try: - super().set_character_set(charset) - except AttributeError: - if self._server_version < (4, 1): - raise NotSupportedError("server is too old to set charset") - self.query("SET NAMES %s" % charset) - self.store_result() - self.encoding = py_charset + """Set the connection character set to charset.""" + super().set_character_set(charset) + self.encoding = _charset_to_encoding.get(charset, charset) def set_sql_mode(self, sql_mode): """Set the connection sql_mode. See MySQL documentation for From 16d139c61f6e927ad950dc5d15da11e4b4dd994e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 13:19:58 +0900 Subject: [PATCH 285/396] Remove escape() and escape_string() from MySQLdb. (#511) --- MySQLdb/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index f56d8101..b567363b 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -38,8 +38,6 @@ string_literal, MySQLError, DataError, - escape, - escape_string, DatabaseError, InternalError, Warning, @@ -164,8 +162,6 @@ def Connect(*args, **kwargs): "converters", "cursors", "debug", - "escape", - "escape_string", "get_client_info", "paramstyle", "string_literal", From 204fb123683454cdb670e0065f09e50d425b94c8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 15:04:33 +0900 Subject: [PATCH 286/396] _mysql: db -> database, passwd -> password (#513) --- MySQLdb/_mysql.c | 2 +- MySQLdb/connections.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 27880ca2..0f6e9f5c 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -414,7 +414,7 @@ _mysql_ConnectionObject_Initialize( *db = NULL, *unix_socket = NULL; unsigned int port = 0; unsigned int client_flag = 0; - static char *kwlist[] = { "host", "user", "passwd", "db", "port", + static char *kwlist[] = { "host", "user", "password", "database", "port", "unix_socket", "conv", "connect_timeout", "compress", "named_pipe", "init_command", diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 482b12f0..89cb7c9a 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -148,10 +148,10 @@ class object, used to create cursors (keyword only) kwargs2 = kwargs.copy() - if "database" in kwargs2: - kwargs2["db"] = kwargs2.pop("database") - if "password" in kwargs2: - kwargs2["passwd"] = kwargs2.pop("password") + if "db" in kwargs2: + kwargs2["database"] = kwargs2.pop("db") + if "passwd" in kwargs2: + kwargs2["password"] = kwargs2.pop("passwd") if "conv" in kwargs: conv = kwargs["conv"] From 5c04abf87d32a3254dd481c03740a8c56520bc3a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 15:25:19 +0900 Subject: [PATCH 287/396] Always set MULTI_RESULTS flag (#515) --- MySQLdb/connections.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 89cb7c9a..38324665 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -173,19 +173,10 @@ class object, used to create cursors (keyword only) self._binary_prefix = kwargs2.pop("binary_prefix", False) client_flag = kwargs.get("client_flag", 0) - - client_version = tuple( - [numeric_part(n) for n in _mysql.get_client_info().split(".")[:2]] - ) - + client_flag |= CLIENT.MULTI_RESULTS multi_statements = kwargs2.pop("multi_statements", True) if multi_statements: - if client_version >= (4, 1): - client_flag |= CLIENT.MULTI_STATEMENTS - - if client_version >= (5, 0): - client_flag |= CLIENT.MULTI_RESULTS - + client_flag |= CLIENT.MULTI_STATEMENTS kwargs2["client_flag"] = client_flag # PEP-249 requires autocommit to be initially off From cda3b37e9ef894c8913e33620471580fcf13c09a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 15:58:58 +0900 Subject: [PATCH 288/396] Actions: Add Python 3.10 and remove 3.5. (#516) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5d45b6a5..e3c0fec1 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - name: Start MySQL run: | From f837c5e00df1115002b4361f1b5ce6b07076faf5 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 15:59:17 +0900 Subject: [PATCH 289/396] Update metadata to support Python 3.10. --- metadata.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/metadata.cfg b/metadata.cfg index 2a2e64fa..c68840b2 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -24,6 +24,7 @@ classifiers: Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: From 4b48f627b31a34250188dca6e4f73cea3fee2ac1 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 16:53:38 +0900 Subject: [PATCH 290/396] 2.1.0rc1 --- metadata.cfg | 4 ++-- setup_common.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/metadata.cfg b/metadata.cfg index c68840b2..81523713 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.0.3 -version_info: (2,0,3,'final',0) +version: 2.1.0rc1 +version_info: (2,1,0,'rc',1) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com diff --git a/setup_common.py b/setup_common.py index 28c51829..60ad30f7 100644 --- a/setup_common.py +++ b/setup_common.py @@ -26,12 +26,11 @@ def enabled(options, option): def create_release_file(metadata): - with open("MySQLdb/release.py", "w") as rel: + with open("MySQLdb/release.py", "w", encoding="utf-8") as rel: rel.write( """ __author__ = "%(author)s <%(author_email)s>" version_info = %(version_info)s __version__ = "%(version)s" -""" - % metadata +""" % metadata ) From 8ff7f8f355bebd0603dadb31dd6590b1f84a1a0b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 17:00:34 +0900 Subject: [PATCH 291/396] Update HISTORY --- HISTORY.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 16fcbd59..0b39d23a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,22 @@ +====================== + What's new in 2.1.0 +====================== + +Release: 2021-10-19 (rc1) + +* Add ``multistatement=True`` option. You can disable multi statement. (#500). +* Remove unnecessary bytes encoder which is remained for Django 1.11 + compatibility (#490). +* Deprecate ``passwd`` and ``db`` keyword. Use ``password`` and ``database`` + instead. (#488). +* Windows: Binary wheels are built with MariaDB Connector/C 3.2.4. (#508) +* ``set_character_set()`` sends ``SET NAMES`` query always. This means + all new connections send it too. This solves compatibility issues + when server and client library are different version. (#509) +* Remove ``escape()`` and ``escape_string()`` from ``MySQLdb`` package. + (#511) +* Add Python 3.10 support and drop Python 3.5 support. + ====================== What's new in 2.0.3 ====================== From 267e784e5abe202862e679b27705c9ae03c7d096 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Oct 2021 17:02:44 +0900 Subject: [PATCH 292/396] black --- setup_common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup_common.py b/setup_common.py index 60ad30f7..5b6927ac 100644 --- a/setup_common.py +++ b/setup_common.py @@ -32,5 +32,6 @@ def create_release_file(metadata): __author__ = "%(author)s <%(author_email)s>" version_info = %(version_info)s __version__ = "%(version)s" -""" % metadata +""" + % metadata ) From 8b920774f27d9d17bc85d1593e5d4a36a7ce3f65 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 20 Oct 2021 16:34:39 +0900 Subject: [PATCH 293/396] Action: Run Django tests (#519) --- .github/workflows/django.yaml | 53 +++++++++++++++++++++++++++++ .travis.yml | 63 ----------------------------------- ci/test_mysql.py | 8 ++--- 3 files changed, 57 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/django.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml new file mode 100644 index 00000000..4e18374a --- /dev/null +++ b/.github/workflows/django.yaml @@ -0,0 +1,53 @@ +name: Django compat test + +on: + push: + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Start MySQL + run: | + sudo systemctl start mysql.service + mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql + mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" + + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-django-pip-1 + restore-keys: | + ${{ runner.os }}-pip- + + - name: Set up Python + uses: actions/setup-python@v2 + with: + # https://www.mail-archive.com/django-updates@googlegroups.com/msg209056.html + python-version: "3.8" + + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + + - name: Install mysqlclient + env: + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_DISABLE_PIP_VERSION_CHECK: 1 + run: | + pip install -U pytest pytest-cov tblib + pip install . + # pip install mysqlclient # Use stable version + + - name: Run Django test + env: + DJANGO_VERSION: "2.2.24" + run: | + sudo apt-get install libmemcached-dev + wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz + tar xf ${DJANGO_VERSION}.tar.gz + cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ + cd django-${DJANGO_VERSION}/tests/ + pip install .. + pip install -r requirements/py3.txt + PYTHONPATH=.. python3 ./runtests.py --settings=test_mysql diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 75c6d425..00000000 --- a/.travis.yml +++ /dev/null @@ -1,63 +0,0 @@ -dist: bionic -language: python - -# See aws s3 ls s3://travis-python-archives/binaries/ubuntu/18.04/x86_64/ -python: - - "nightly" - - "pypy3" - -cache: pip - -services: - - mysql - -install: - - pip install -U pip - - pip install -U mock coverage pytest pytest-cov codecov - -env: - global: - - TESTDB=travis.cnf - -before_script: - - "mysql --help" - - "mysql --print-defaults" - - "mysql -e 'create database mysqldb_test charset utf8mb4;'" - -script: - - pip install -e . - - pytest --cov ./MySQLdb - -after_success: - - codecov - -jobs: - fast_finish: true - include: - - &django_2_2 - name: "Django 2.2 test" - env: - - DJANGO_VERSION=2.2.7 - python: "3.8" - install: - - pip install -U pip - - wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz - - tar xf ${DJANGO_VERSION}.tar.gz - - pip install -e django-${DJANGO_VERSION}/ - - cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ - - pip install . - - before_script: - - mysql -e 'create user django identified by "secret"' - - mysql -e 'grant all on *.* to django' - - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql mysql - - script: - - cd django-${DJANGO_VERSION}/tests/ - - ./runtests.py --parallel=2 --settings=test_mysql - #- &django_3_0 - # <<: *django_2_2 - # name: "Django 3.0 test (Python 3.8)" - # python: "3.8" - -# vim: sw=2 ts=2 sts=2 diff --git a/ci/test_mysql.py b/ci/test_mysql.py index 88a747a6..e285f4cf 100644 --- a/ci/test_mysql.py +++ b/ci/test_mysql.py @@ -16,17 +16,17 @@ "default": { "ENGINE": "django.db.backends.mysql", "NAME": "django_default", - "USER": "django", "HOST": "127.0.0.1", - "PASSWORD": "secret", + "USER": "scott", + "PASSWORD": "tiger", "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, }, "other": { "ENGINE": "django.db.backends.mysql", "NAME": "django_other", - "USER": "django", "HOST": "127.0.0.1", - "PASSWORD": "secret", + "USER": "scott", + "PASSWORD": "tiger", "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, }, } From 6ebc1a1972dee69fb54b56867fc795ee220b5d79 Mon Sep 17 00:00:00 2001 From: Kian Meng Ang Date: Wed, 17 Nov 2021 16:38:58 +0800 Subject: [PATCH 294/396] Fix typos (#520) --- HISTORY.rst | 4 ++-- MySQLdb/_mysql.c | 2 +- MySQLdb/cursors.py | 4 ++-- doc/user_guide.rst | 2 +- tests/dbapi20.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0b39d23a..8bd4c2cc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -94,7 +94,7 @@ Release: 2019-08-09 * ``--static`` build supports ``libmariadbclient.a`` * Try ``mariadb_config`` when ``mysql_config`` is not found -* Fixed warning happend in Python 3.8 (#359) +* Fixed warning happened in Python 3.8 (#359) * Fixed ``from MySQLdb import *``, while I don't recommend it. (#369) * Fixed SEGV ``MySQLdb.escape_string("1")`` when libmariadb is used and no connection is created. (#367) @@ -294,7 +294,7 @@ More tests for date and time columns. (#41) Fix calling .execute() method for closed cursor cause TypeError. (#37) -Improve peformance to parse date. (#43) +Improve performance to parse date. (#43) Support geometry types (#49) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 0f6e9f5c..f10cd015 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -310,7 +310,7 @@ _mysql_ResultObject_Initialize( PyObject *fun2=NULL; int j, n2=PySequence_Size(fun); // BINARY_FLAG means ***_bin collation is used. - // To distinguish text and binary, we shoud use charsetnr==63 (binary). + // To distinguish text and binary, we should use charsetnr==63 (binary). // But we abuse BINARY_FLAG for historical reason. if (fields[i].charsetnr == 63) { flags |= BINARY_FLAG; diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 451dab5f..f8a48640 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -375,7 +375,7 @@ def fetchmany(self, size=None): return result def fetchall(self): - """Fetchs all available rows from the cursor.""" + """Fetches all available rows from the cursor.""" self._check_executed() if self.rownumber: result = self._rows[self.rownumber :] @@ -437,7 +437,7 @@ def fetchmany(self, size=None): return r def fetchall(self): - """Fetchs all available rows from the cursor.""" + """Fetches all available rows from the cursor.""" self._check_executed() r = self._fetch_row(0) self.rownumber = self.rownumber + len(r) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index a00f292c..555adf15 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -511,7 +511,7 @@ callproc(procname, args) can only be returned with a SELECT statement. Since a stored procedure may return zero or more result sets, it is impossible for MySQLdb to determine if there are result sets to fetch - before the modified parmeters are accessible. + before the modified parameters are accessible. The parameters are stored in the server as @_*procname*_*n*, where *n* is the position of the parameter. I.e., if you diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 4824d9cc..4965c9bf 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -793,7 +793,7 @@ def test_setoutputsize_basic(self): con.close() def test_setoutputsize(self): - # Real test for setoutputsize is driver dependant + # Real test for setoutputsize is driver dependent raise NotImplementedError("Driver need to override this test") def test_None(self): From 5340191feb16b1f99a7b43fe7c74a2f690138cb1 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 17 Nov 2021 17:46:52 +0900 Subject: [PATCH 295/396] 2.1.0 --- HISTORY.rst | 2 +- metadata.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 8bd4c2cc..19d57bee 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,7 +2,7 @@ What's new in 2.1.0 ====================== -Release: 2021-10-19 (rc1) +Release: 2021-11-17 * Add ``multistatement=True`` option. You can disable multi statement. (#500). * Remove unnecessary bytes encoder which is remained for Django 1.11 diff --git a/metadata.cfg b/metadata.cfg index 81523713..95433ffb 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.1.0rc1 -version_info: (2,1,0,'rc',1) +version: 2.1.0 +version_info: (2,1,0,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 2316313fc6777591494e870ba0c5da0d3dcd6dc6 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 10 Jan 2022 10:44:49 +0900 Subject: [PATCH 296/396] Add __module__ attributes to exception classes. (#523) --- MySQLdb/_exceptions.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/MySQLdb/_exceptions.py b/MySQLdb/_exceptions.py index ba35deaf..a5aca7e1 100644 --- a/MySQLdb/_exceptions.py +++ b/MySQLdb/_exceptions.py @@ -9,32 +9,44 @@ class MySQLError(Exception): """Exception related to operation with MySQL.""" + __module__ = "MySQLdb" + class Warning(Warning, MySQLError): """Exception raised for important warnings like data truncations while inserting, etc.""" + __module__ = "MySQLdb" + class Error(MySQLError): """Exception that is the base class of all other error exceptions (not Warning).""" + __module__ = "MySQLdb" + class InterfaceError(Error): """Exception raised for errors that are related to the database interface rather than the database itself.""" + __module__ = "MySQLdb" + class DatabaseError(Error): """Exception raised for errors that are related to the database.""" + __module__ = "MySQLdb" + class DataError(DatabaseError): """Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range, etc.""" + __module__ = "MySQLdb" + class OperationalError(DatabaseError): """Exception raised for errors that are related to the database's @@ -43,27 +55,37 @@ class OperationalError(DatabaseError): found, a transaction could not be processed, a memory allocation error occurred during processing, etc.""" + __module__ = "MySQLdb" + class IntegrityError(DatabaseError): """Exception raised when the relational integrity of the database is affected, e.g. a foreign key check fails, duplicate key, etc.""" + __module__ = "MySQLdb" + class InternalError(DatabaseError): """Exception raised when the database encounters an internal error, e.g. the cursor is not valid anymore, the transaction is out of sync, etc.""" + __module__ = "MySQLdb" + class ProgrammingError(DatabaseError): """Exception raised for programming errors, e.g. table not found or already exists, syntax error in the SQL statement, wrong number of parameters specified, etc.""" + __module__ = "MySQLdb" + class NotSupportedError(DatabaseError): """Exception raised in case a method or database API was used which is not supported by the database, e.g. requesting a .rollback() on a connection that does not support transaction or has transactions turned off.""" + + __module__ = "MySQLdb" From 9729618950577b3352f3569d17564ee06d96f708 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 15 Apr 2022 20:18:03 +0900 Subject: [PATCH 297/396] Create SECURITY.md --- SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..a54d21b1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +2.1.x + +## Reporting a Vulnerability + +email: songofacandy@gmail.com From c1c812a1dfeb2a8b3e08ff56058dd3365e0937f5 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 17 Apr 2022 10:21:21 +0900 Subject: [PATCH 298/396] Fix out of range bug (#538) --- MySQLdb/_mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index f10cd015..2c02d765 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1442,7 +1442,7 @@ _mysql_ResultObject_fetch_row( &maxrows, &how)) return NULL; check_result_connection(self); - if (how >= (int)sizeof(row_converters)) { + if (how >= (int)(sizeof(row_converters) / sizeof(row_converters[0]))) { PyErr_SetString(PyExc_ValueError, "how out of range"); return NULL; } From 6979a47f2e0f444e988aeadb1fbfceaeb45fd3c3 Mon Sep 17 00:00:00 2001 From: Llewyllen <61664783+Llewyllen@users.noreply.github.com> Date: Thu, 2 Jun 2022 05:57:40 +0200 Subject: [PATCH 299/396] Fix docstring for _mysql.connect (#540) --- MySQLdb/_mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 2c02d765..7737dbe7 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -581,10 +581,10 @@ host\n\ user\n\ string, user to connect as\n\ \n\ -passwd\n\ +password\n\ string, password to use\n\ \n\ -db\n\ +database\n\ string, database to use\n\ \n\ port\n\ From aafdec84cf34155d69e584f138c7a4a7984bf77b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 22 Jun 2022 09:07:56 +0900 Subject: [PATCH 300/396] Action: Update Windows wheel build (#541) --- .github/workflows/windows.yaml | 36 ++++++++++------------------------ 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index ac9b28da..e2314b44 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -4,13 +4,14 @@ on: push: branches: - master + - ci workflow_dispatch: jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.2.4" + CONNECTOR_VERSION: "3.3.1" steps: - name: Cache Connector @@ -57,36 +58,19 @@ jobs: EOF cat site.cfg + - uses: actions/setup-python@v2 + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.7.0 - name: Build wheels - shell: cmd working-directory: mysqlclient - run: | - py -3.10 -m pip install -U setuptools wheel pip - py -3.10 setup.py bdist_wheel - py -3.9 -m pip install -U setuptools wheel pip - py -3.9 setup.py bdist_wheel - py -3.8 -m pip install -U setuptools wheel pip - py -3.8 setup.py bdist_wheel - py -3.7 -m pip install -U setuptools wheel pip - py -3.7 setup.py bdist_wheel + env: + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.8" + CIBW_ARCHS: "AMD64" + CIBW_TEST_COMMAND: "python -c \"import MySQLdb; print(MySQLdb.version_info)\" " + run: "python -m cibuildwheel --prerelease-pythons --output-dir dist" - name: Upload Wheel uses: actions/upload-artifact@v2 with: name: win-wheels path: mysqlclient/dist/*.whl - - - name: Check wheels - shell: bash - working-directory: mysqlclient/dist - run: | - ls -la - py -3.10 -m pip install --no-index --find-links . mysqlclient - py -3.10 -c "import MySQLdb; print(MySQLdb.version_info)" - py -3.9 -m pip install --no-index --find-links . mysqlclient - py -3.9 -c "import MySQLdb; print(MySQLdb.version_info)" - py -3.8 -m pip install --no-index --find-links . mysqlclient - py -3.8 -c "import MySQLdb; print(MySQLdb.version_info)" - py -3.7 -m pip install --no-index --find-links . mysqlclient - py -3.7 -c "import MySQLdb; print(MySQLdb.version_info)" - From 78caa9ed05d8c27a27abe5eafd96d782279f1fca Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 22 Jun 2022 10:22:08 +0900 Subject: [PATCH 301/396] v2.1.1 --- HISTORY.rst | 14 ++++++++++++++ metadata.cfg | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 19d57bee..674c6881 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,17 @@ +====================== + What's new in 2.1.1 +====================== + +Release: 2022-06-22 + +* Fix qualname of exception classes. (#522) +* Fix range check in ``MySQLdb._mysql.result.fetch_row()``. Invalid ``how`` argument caused SEGV. (#538) +* Fix docstring of ``_mysql.connect``. (#540) +* Windows: Binary wheels are updated. (#541) + * Use MariaDB Connector/C 3.3.1. + * Use cibuildwheel to build wheels. + * Python 3.8-3.11 + ====================== What's new in 2.1.0 ====================== diff --git a/metadata.cfg b/metadata.cfg index 95433ffb..5bf1e815 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 2.1.0 -version_info: (2,1,0,'final',0) +version: 2.1.1 +version_info: (2,1,1,'final',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 684dcbf0657f18c1ba12fe21732323c890ff20ab Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 22 Jun 2022 18:07:11 +0900 Subject: [PATCH 302/396] Actions: Drop Python 3.6 and add 3.11-dev (#542) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e3c0fec1..1341e0f4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] steps: - name: Start MySQL run: | From dac24e7b05b83eb1511e25e3cd8f8c20b7fbe112 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 22 Jun 2022 18:08:05 +0900 Subject: [PATCH 303/396] Update metadata --- metadata.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata.cfg b/metadata.cfg index 5bf1e815..0d35d8fc 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -20,11 +20,11 @@ classifiers: Programming Language :: C Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Database Topic :: Database :: Database Engines/Servers py_modules: From f5a2f3dd280679bd6e33d3f44d2121ae485e956f Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 5 Aug 2022 12:40:27 +1000 Subject: [PATCH 304/396] docs: fix simple typo, portible -> portable (#547) There is a small typo in tests/dbapi20.py. Should read `portable` rather than `portible`. --- tests/dbapi20.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 4965c9bf..a88a0616 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -56,7 +56,7 @@ # - self.populate is now self._populate(), so if a driver stub # overrides self.ddl1 this change propagates # - VARCHAR columns now have a width, which will hopefully make the -# DDL even more portible (this will be reversed if it causes more problems) +# DDL even more portable (this will be reversed if it causes more problems) # - cursor.rowcount being checked after various execute and fetchXXX methods # - Check for fetchall and fetchmany returning empty lists after results # are exhausted (already checking for empty lists if select retrieved From d288d3e224e68a6e1736282d09ec74a50c227530 Mon Sep 17 00:00:00 2001 From: gopackgo90 Date: Mon, 19 Sep 2022 02:23:55 -0500 Subject: [PATCH 305/396] Update python_requires to 3.7+ (#543) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index dfa661c1..aa6c34fb 100644 --- a/setup.py +++ b/setup.py @@ -18,5 +18,5 @@ ] metadata["long_description"] = readme metadata["long_description_content_type"] = "text/markdown" -metadata["python_requires"] = ">=3.5" +metadata["python_requires"] = ">=3.7" setuptools.setup(**metadata) From 8f0cbacba853f0328b839b742b32a0ebb0687b8e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 20 Sep 2022 14:10:19 +0900 Subject: [PATCH 306/396] Raise ProgrammingError on -inf (#557) --- MySQLdb/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 33f22f74..d6fdc01c 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -72,7 +72,7 @@ def Thing2Str(s, d): def Float2Str(o, d): s = repr(o) - if s in ("inf", "nan"): + if s in ("inf", "-inf", "nan"): raise ProgrammingError("%s can not be used with MySQL" % s) if "e" not in s: s += "e0" From 6f5fca04fe58c7ddd41c2a64b9c00cb0e167f4b3 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 28 Oct 2022 01:08:39 -0500 Subject: [PATCH 307/396] swap 3.11-dev for 3.11 in CI (#561) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1341e0f4..0b34ecb4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - name: Start MySQL run: | From 0914c8cd778f27fccce6f3895d80bfaa64520d47 Mon Sep 17 00:00:00 2001 From: "lgtm-com[bot]" <43144390+lgtm-com[bot]@users.noreply.github.com> Date: Thu, 10 Nov 2022 15:09:40 +0900 Subject: [PATCH 308/396] Add CodeQL workflow for GitHub code scanning (#565) Co-authored-by: LGTM Migrator --- .github/workflows/codeql.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..90a8e5b0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: "29 15 * * 6" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python, cpp ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + if: ${{ matrix.language == 'python' || matrix.language == 'cpp' }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From da4c072431a2eaf533226139a00b3aa43fbd3d6c Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 17 Jan 2023 17:23:07 +0900 Subject: [PATCH 309/396] Add .readthedocs.yaml --- .readthedocs.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..1a7c18e8 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,9 @@ +version: 2 + +python: + version: 3.9 + +build: + apt_packages: + - default-libmysqlclient-dev + - build-essential From 589740a67a2e82fe80489e984d625cf84a36c875 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 17 Jan 2023 17:24:49 +0900 Subject: [PATCH 310/396] RTD: Use Python 3.8 --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1a7c18e8..26440026 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,7 @@ version: 2 python: - version: 3.9 + version: 3.8 build: apt_packages: From cc1b042d8a0fafbbaa389cb1fad1704a498051d3 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 17 Jan 2023 17:26:59 +0900 Subject: [PATCH 311/396] RTD: Use ubuntu-22.04 --- .readthedocs.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 26440026..ec91283e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,9 @@ version: 2 -python: - version: 3.8 +build: + os: ubuntu-22.04 + tools: + python: "3.11" build: apt_packages: From b419beab9e60dd5053a8de2864e0c668bb1a6caf Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 17 Jan 2023 17:33:50 +0900 Subject: [PATCH 312/396] RTD: Fix yaml --- .readthedocs.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ec91283e..eaffaf39 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,8 +4,6 @@ build: os: ubuntu-22.04 tools: python: "3.11" - -build: apt_packages: - default-libmysqlclient-dev - build-essential From 58465cfa8704fc51fe3173254d7b822abb30e575 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 13 Mar 2023 19:09:17 +0900 Subject: [PATCH 313/396] ER_BAD_NULL should be IntegrityError. (#579) Fixes #535 --- MySQLdb/_mysql.c | 1 + tests/capabilities.py | 3 -- tests/test_MySQLdb_capabilities.py | 1 - tests/test_errors.py | 56 ++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/test_errors.py diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 7737dbe7..6c04ec99 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -180,6 +180,7 @@ _mysql_Exception(_mysql_ConnectionObject *c) #ifdef ER_NO_DEFAULT_FOR_FIELD case ER_NO_DEFAULT_FOR_FIELD: #endif + case ER_BAD_NULL_ERROR: e = _mysql_IntegrityError; break; #ifdef ER_WARNING_NOT_COMPLETE_ROLLBACK diff --git a/tests/capabilities.py b/tests/capabilities.py index da753d15..034e88da 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -11,7 +11,6 @@ class DatabaseTest(unittest.TestCase): - db_module = None connect_args = () connect_kwargs = dict() @@ -20,7 +19,6 @@ class DatabaseTest(unittest.TestCase): debug = False def setUp(self): - db = connection_factory(**self.connect_kwargs) self.connection = db self.cursor = db.cursor() @@ -67,7 +65,6 @@ def new_table_name(self): i = i + 1 def create_table(self, columndefs): - """Create a table using a list of column definitions given in columndefs. diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index fc213b84..dbff27c2 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -12,7 +12,6 @@ class test_MySQLdb(capabilities.DatabaseTest): - db_module = MySQLdb connect_args = () connect_kwargs = dict( diff --git a/tests/test_errors.py b/tests/test_errors.py new file mode 100644 index 00000000..3a9fecaa --- /dev/null +++ b/tests/test_errors.py @@ -0,0 +1,56 @@ +import pytest +import MySQLdb.cursors +from configdb import connection_factory + + +_conns = [] +_tables = [] + + +def connect(**kwargs): + conn = connection_factory(**kwargs) + _conns.append(conn) + return conn + + +def teardown_function(function): + if _tables: + c = _conns[0] + cur = c.cursor() + for t in _tables: + cur.execute("DROP TABLE {}".format(t)) + cur.close() + del _tables[:] + + for c in _conns: + c.close() + del _conns[:] + + +def test_null(): + """Inserting NULL into non NULLABLE column""" + # https://github.com/PyMySQL/mysqlclient/issues/535 + table_name = "test_null" + conn = connect() + cursor = conn.cursor() + + cursor.execute(f"create table {table_name} (c1 int primary key)") + _tables.append(table_name) + + with pytest.raises(MySQLdb.IntegrityError): + cursor.execute(f"insert into {table_name} values (null)") + + +def test_duplicated_pk(): + """Inserting row with duplicated PK""" + # https://github.com/PyMySQL/mysqlclient/issues/535 + table_name = "test_duplicated_pk" + conn = connect() + cursor = conn.cursor() + + cursor.execute(f"create table {table_name} (c1 int primary key)") + _tables.append(table_name) + + cursor.execute(f"insert into {table_name} values (1)") + with pytest.raises(MySQLdb.IntegrityError): + cursor.execute(f"insert into {table_name} values (1)") From 17c4e466d9b752c4a71362ef5b0c4b8681de4362 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 1 Apr 2023 00:01:00 +0900 Subject: [PATCH 314/396] Update security policy. --- SECURITY.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index a54d21b1..75f0c541 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,5 @@ -# Security Policy +## Security contact information -## Supported Versions - -2.1.x - -## Reporting a Vulnerability - -email: songofacandy@gmail.com +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. \ No newline at end of file From d0658273acc9e8b929d7e8885487da41ef6461cc Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 7 May 2023 10:33:42 +0900 Subject: [PATCH 315/396] Update windows build workflow (#585) Use MariaDB Connector/C 3.3.4 --- .github/workflows/windows.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e2314b44..bd711075 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -11,12 +11,12 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.3.1" + CONNECTOR_VERSION: "3.3.4" steps: - name: Cache Connector id: cache-connector - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: c:/mariadb-connector key: mariadb-connector-c-${{ env.CONNECTOR_VERSION }}-win @@ -41,7 +41,7 @@ jobs: cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake - name: Checkout mysqlclient - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: mysqlclient @@ -58,9 +58,9 @@ jobs: EOF cat site.cfg - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.7.0 + run: python -m pip install cibuildwheel==2.12.3 - name: Build wheels working-directory: mysqlclient env: @@ -70,7 +70,7 @@ jobs: run: "python -m cibuildwheel --prerelease-pythons --output-dir dist" - name: Upload Wheel - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: win-wheels path: mysqlclient/dist/*.whl From 14538b2ccde0b1287d019ddf674c27e71213c735 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 10:36:25 +0900 Subject: [PATCH 316/396] Use pkg-config instead of mysql_config (#586) MySQL breaks mysql_config often. Use pkg-config instead. Fixes #584 --- README.md | 2 +- metadata.cfg | 5 +- setup_common.py | 4 +- setup_posix.py | 178 ++++++++++++++--------------------------------- setup_windows.py | 14 ++-- site.cfg | 5 -- 6 files changed, 65 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index 4dbc54d7..23db1f27 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ $ pip install mysqlclient ### Customize build (POSIX) -mysqlclient uses `mysql_config` or `mariadb_config` by default for finding +mysqlclient uses `pkg-config --clfags --ldflags mysqlclient` by default for finding compiler/linker flags. You can use `MYSQLCLIENT_CFLAGS` and `MYSQLCLIENT_LDFLAGS` environment diff --git a/metadata.cfg b/metadata.cfg index 0d35d8fc..4d5e0174 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,6 +1,7 @@ [metadata] -version: 2.1.1 -version_info: (2,1,1,'final',0) +name: mysqlclient +version: 2.1.2 +version_info: (2,1,2,'dev',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com diff --git a/setup_common.py b/setup_common.py index 5b6927ac..8d7a37d5 100644 --- a/setup_common.py +++ b/setup_common.py @@ -1,8 +1,8 @@ -from configparser import ConfigParser as SafeConfigParser +from configparser import ConfigParser def get_metadata_and_options(): - config = SafeConfigParser() + config = ConfigParser() config.read(["metadata.cfg", "site.cfg"]) metadata = dict(config.items("metadata")) diff --git a/setup_posix.py b/setup_posix.py index 99763cbc..a03dd22c 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -1,61 +1,29 @@ import os import sys +import subprocess -# This dequote() business is required for some older versions -# of mysql_config - -def dequote(s): - if not s: - raise Exception( - "Wrong MySQL configuration: maybe https://bugs.mysql.com/bug.php?id=86971 ?" - ) - if s[0] in "\"'" and s[0] == s[-1]: - s = s[1:-1] - return s - - -_mysql_config_path = "mysql_config" - - -def mysql_config(what): - cmd = "{} --{}".format(_mysql_config_path, what) - print(cmd) - f = os.popen(cmd) - data = f.read().strip().split() - ret = f.close() - if ret: - if ret / 256: - data = [] - if ret / 256 > 1: - raise OSError("{} not found".format(_mysql_config_path)) - print(data) - return data +def find_package_name(): + """Get available pkg-config package name""" + packages = ["mysqlclient", "mariadb"] + for pkg in packages: + try: + cmd = f"pkg-config --exists {pkg}" + print(f"Trying {cmd}") + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as err: + print(err) + else: + return pkg + raise Exception("Can not find valid pkg-config") def get_config(): from setup_common import get_metadata_and_options, enabled, create_release_file - global _mysql_config_path - metadata, options = get_metadata_and_options() - if "mysql_config" in options: - _mysql_config_path = options["mysql_config"] - else: - try: - mysql_config("version") - except OSError: - # try mariadb_config - _mysql_config_path = "mariadb_config" - try: - mysql_config("version") - except OSError: - _mysql_config_path = "mysql_config" - - extra_objects = [] static = enabled(options, "static") - # allow a command-line option to override the base config file to permit # a static build to be created via requirements.txt # @@ -63,106 +31,64 @@ def get_config(): static = True sys.argv.remove("--static") - libs = os.environ.get("MYSQLCLIENT_LDFLAGS") - if libs: - libs = libs.strip().split() - else: - libs = mysql_config("libs") - library_dirs = [dequote(i[2:]) for i in libs if i.startswith("-L")] - libraries = [dequote(i[2:]) for i in libs if i.startswith("-l")] - extra_link_args = [x for x in libs if not x.startswith(("-l", "-L"))] - + ldflags = os.environ.get("MYSQLCLIENT_LDFLAGS") cflags = os.environ.get("MYSQLCLIENT_CFLAGS") - if cflags: - use_mysqlconfig_cflags = False - cflags = cflags.strip().split() - else: - use_mysqlconfig_cflags = True - cflags = mysql_config("cflags") - - include_dirs = [] - extra_compile_args = ["-std=c99"] - for a in cflags: - if a.startswith("-I"): - include_dirs.append(dequote(a[2:])) - elif a.startswith(("-L", "-l")): # This should be LIBS. - pass - else: - extra_compile_args.append(a.replace("%", "%%")) - - # Copy the arch flags for linking as well - try: - i = extra_compile_args.index("-arch") - if "-arch" not in extra_link_args: - extra_link_args += ["-arch", extra_compile_args[i + 1]] - except ValueError: - pass + pkg_name = None + static_opt = " --static" if static else "" + if not (cflags and ldflags): + pkg_name = find_package_name() + if not cflags: + cflags = subprocess.check_output( + f"pkg-config{static_opt} --cflags {pkg_name}", encoding="utf-8", shell=True + ) + if not ldflags: + ldflags = subprocess.check_output( + f"pkg-config{static_opt} --libs {pkg_name}", encoding="utf-8", shell=True + ) - if static: - # properly handle mysql client libraries that are not called libmysqlclient - client = None - CLIENT_LIST = [ - "mysqlclient", - "mysqlclient_r", - "mysqld", - "mariadb", - "mariadbclient", - "perconaserverclient", - "perconaserverclient_r", - ] - for c in CLIENT_LIST: - if c in libraries: - client = c - break - - if client == "mariadb": - client = "mariadbclient" - if client is None: - raise ValueError("Couldn't identify mysql client library") - - extra_objects.append(os.path.join(library_dirs[0], "lib%s.a" % client)) - if client in libraries: - libraries.remove(client) + cflags = cflags.split() + for f in cflags: + if f.startswith("-std="): + break else: - if use_mysqlconfig_cflags: - # mysql_config may have "-lmysqlclient -lz -lssl -lcrypto", but zlib and - # ssl is not used by _mysql. They are needed only for static build. - for L in ("crypto", "ssl", "z", "zstd"): - if L in libraries: - libraries.remove(L) + cflags += ["-std=c99"] - name = "mysqlclient" - metadata["name"] = name + ldflags = ldflags.split() define_macros = [ ("version_info", metadata["version_info"]), ("__version__", metadata["version"]), ] - create_release_file(metadata) - del metadata["version_info"] + + # print(f"{cflags = }") + # print(f"{ldflags = }") + # print(f"{define_macros = }") + ext_options = dict( - library_dirs=library_dirs, - libraries=libraries, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - include_dirs=include_dirs, - extra_objects=extra_objects, + extra_compile_args=cflags, + extra_link_args=ldflags, define_macros=define_macros, ) - # newer versions of gcc require libstdc++ if doing a static build if static: ext_options["language"] = "c++" - print("ext_options:") + print("Options for building extention module:") for k, v in ext_options.items(): - print(" {}: {}".format(k, v)) + print(f" {k}: {v}") + + create_release_file(metadata) + del metadata["version_info"] return metadata, ext_options if __name__ == "__main__": - sys.stderr.write( - """You shouldn't be running this directly; it is used by setup.py.""" - ) + from pprint import pprint + + metadata, config = get_config() + print("# Metadata") + pprint(metadata, sort_dicts=False, compact=True) + print("\n# Extention options") + pprint(config, sort_dicts=False, compact=True) diff --git a/setup_windows.py b/setup_windows.py index b2feb7d2..5d8d7158 100644 --- a/setup_windows.py +++ b/setup_windows.py @@ -1,5 +1,4 @@ import os -import sys def get_config(): @@ -38,9 +37,6 @@ def get_config(): extra_link_args = ["/MANIFEST"] - name = "mysqlclient" - metadata["name"] = name - define_macros = [ ("version_info", metadata["version_info"]), ("__version__", metadata["version"]), @@ -59,6 +55,10 @@ def get_config(): if __name__ == "__main__": - sys.stderr.write( - """You shouldn't be running this directly; it is used by setup.py.""" - ) + from pprint import pprint + + metadata, config = get_config() + print("# Metadata") + pprint(metadata) + print("\n# Extention options") + pprint(config) diff --git a/site.cfg b/site.cfg index 08a14b0e..39e3c2b1 100644 --- a/site.cfg +++ b/site.cfg @@ -2,11 +2,6 @@ # static: link against a static library static = False -# The path to mysql_config. -# Only use this if mysql_config is not on your PATH, or you have some weird -# setup that requires it. -#mysql_config = /usr/local/bin/mysql_config - # http://stackoverflow.com/questions/1972259/mysql-python-install-problem-using-virtualenv-windows-pip # Windows connector libs for MySQL. You need a 32-bit connector for your 32-bit Python build. connector = From aed1dd26327d9a5baeeb5704c14c8b5fc8f9f5d8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 11:32:48 +0900 Subject: [PATCH 317/396] Remove uneeded code. (#512) --- MySQLdb/connections.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 38324665..f71a55cd 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -144,7 +144,6 @@ class object, used to create cursors (keyword only) """ from MySQLdb.constants import CLIENT, FIELD_TYPE from MySQLdb.converters import conversions, _bytes_or_str - from weakref import proxy kwargs2 = kwargs.copy() @@ -214,13 +213,6 @@ class object, used to create cursors (keyword only) # MySQL may return JSON with charset==binary. self.converter[FIELD_TYPE.JSON] = str - db = proxy(self) - - def unicode_literal(u, dummy=None): - return db.string_literal(u.encode(db.encoding)) - - self.encoders[str] = unicode_literal - self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: if autocommit is not None: From df52e237b3de45646e24ac542c268211cc2b80a8 Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Mon, 8 May 2023 22:45:28 -0400 Subject: [PATCH 318/396] Add collation option (#564) Fixes #563 --- MySQLdb/connections.py | 16 ++++++++++++++-- doc/user_guide.rst | 16 ++++++++++++++++ tests/test_MySQLdb_nonstandard.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index f71a55cd..f56c4f54 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -97,6 +97,14 @@ class object, used to create cursors (keyword only) If supplied, the connection character set will be changed to this character set. + :param str collation: + If ``charset`` and ``collation`` are both supplied, the + character set and collation for the current connection + will be set. + + If omitted, empty string, or None, the default collation + for the ``charset`` is implied. + :param str auth_plugin: If supplied, the connection default authentication plugin will be changed to this value. Example values: @@ -167,6 +175,7 @@ class object, used to create cursors (keyword only) cursorclass = kwargs2.pop("cursorclass", self.default_cursor) charset = kwargs2.get("charset", "") + collation = kwargs2.pop("collation", "") use_unicode = kwargs2.pop("use_unicode", True) sql_mode = kwargs2.pop("sql_mode", "") self._binary_prefix = kwargs2.pop("binary_prefix", False) @@ -193,7 +202,7 @@ class object, used to create cursors (keyword only) if not charset: charset = self.character_set_name() - self.set_character_set(charset) + self.set_character_set(charset, collation) if sql_mode: self.set_sql_mode(sql_mode) @@ -285,10 +294,13 @@ def begin(self): """ self.query(b"BEGIN") - def set_character_set(self, charset): + def set_character_set(self, charset, collation=None): """Set the connection character set to charset.""" super().set_character_set(charset) self.encoding = _charset_to_encoding.get(charset, charset) + if collation: + self.query("SET NAMES %s COLLATE %s" % (charset, collation)) + self.store_result() def set_sql_mode(self, sql_mode): """Set the connection sql_mode. See MySQL documentation for diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 555adf15..5c9577bc 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -348,6 +348,22 @@ connect(parameters...) *This must be a keyword parameter.* + collation + If ``charset`` and ``collation`` are both supplied, the + character set and collation for the current connection + will be set. + + If omitted, empty string, or None, the default collation + for the ``charset`` is implied by the database server. + + To learn more about the quiddities of character sets and + collations, consult the `MySQL docs + `_ + and `MariaDB docs + `_ + + *This must be a keyword parameter.* + sql_mode If present, the session SQL mode will be set to the given string. For more information on sql_mode, see the MySQL diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index c517dad3..5e841791 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -114,3 +114,33 @@ def test_context_manager(self): with connection_factory() as conn: self.assertFalse(conn.closed) self.assertTrue(conn.closed) + + +class TestCollation(unittest.TestCase): + """Test charset and collation connection options.""" + + def setUp(self): + # Initialize a connection with a non-default character set and + # collation. + self.conn = connection_factory( + charset="utf8mb4", + collation="utf8mb4_esperanto_ci", + ) + + def tearDown(self): + self.conn.close() + + def test_charset_collation(self): + c = self.conn.cursor() + c.execute( + """ + SHOW VARIABLES WHERE + Variable_Name="character_set_connection" OR + Variable_Name="collation_connection"; + """ + ) + row = c.fetchall() + charset = row[0][1] + collation = row[1][1] + self.assertEqual(charset, "utf8mb4") + self.assertEqual(collation, "utf8mb4_esperanto_ci") From c56fc43482a09ec6bb5e21d20baf5a86f89156f5 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 11:45:45 +0900 Subject: [PATCH 319/396] Start 2.2.0 development (#587) --- HISTORY.rst | 10 ++++++++++ metadata.cfg | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 674c6881..13e5cb01 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,13 @@ +====================== + What's new in 2.2.0 +====================== + +Release: TBD + +* Use ``pkg-config`` instead of ``mysql_config`` (#586) + + + ====================== What's new in 2.1.1 ====================== diff --git a/metadata.cfg b/metadata.cfg index 4d5e0174..87ebc6c5 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -1,7 +1,7 @@ [metadata] name: mysqlclient -version: 2.1.2 -version_info: (2,1,2,'dev',0) +version: 2.2.0dev0 +version_info: (2,2,0,'dev',0) description: Python interface to MySQL author: Inada Naoki author_email: songofacandy@gmail.com From 418b68dc5f5d677b595944f71ccf36d12753f7a1 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 13:48:58 +0900 Subject: [PATCH 320/396] Action: Use Ruff (#588) --- .github/workflows/lint.yaml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d6aff95a..77a13c22 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -4,14 +4,8 @@ on: [push, pull_request] jobs: lint: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 - uses: psf/black@stable - - name: Setup flake8 annotations - uses: rbialon/flake8-annotations@v1 - - name: flake8 - run: | - pip install flake8 - flake8 --ignore=E203,E501,W503 --max-line-length=88 . + - uses: chartboost/ruff-action@v1 From 1f906e66c4082c305645ddcded18018cecc302fd Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 14:10:53 +0900 Subject: [PATCH 321/396] Run pyupgrade --py38-plus (#590) --- MySQLdb/connections.py | 2 +- MySQLdb/constants/CR.py | 2 +- MySQLdb/constants/ER.py | 2 +- setup_common.py | 2 +- tests/capabilities.py | 14 +++++++------- tests/dbapi20.py | 3 +-- tests/test_cursor.py | 2 +- tests/test_errors.py | 2 +- 8 files changed, 14 insertions(+), 15 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index f56c4f54..865d129a 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -299,7 +299,7 @@ def set_character_set(self, charset, collation=None): super().set_character_set(charset) self.encoding = _charset_to_encoding.get(charset, charset) if collation: - self.query("SET NAMES %s COLLATE %s" % (charset, collation)) + self.query(f"SET NAMES {charset} COLLATE {collation}") self.store_result() def set_sql_mode(self, sql_mode): diff --git a/MySQLdb/constants/CR.py b/MySQLdb/constants/CR.py index 9d33cf65..9467ae11 100644 --- a/MySQLdb/constants/CR.py +++ b/MySQLdb/constants/CR.py @@ -29,7 +29,7 @@ data[value].add(name) for value, names in sorted(data.items()): for name in sorted(names): - print("{} = {}".format(name, value)) + print(f"{name} = {value}") if error_last is not None: print("ERROR_LAST = %s" % error_last) diff --git a/MySQLdb/constants/ER.py b/MySQLdb/constants/ER.py index fcd5bf2e..8c5ece24 100644 --- a/MySQLdb/constants/ER.py +++ b/MySQLdb/constants/ER.py @@ -30,7 +30,7 @@ data[value].add(name) for value, names in sorted(data.items()): for name in sorted(names): - print("{} = {}".format(name, value)) + print(f"{name} = {value}") if error_last is not None: print("ERROR_LAST = %s" % error_last) diff --git a/setup_common.py b/setup_common.py index 8d7a37d5..53869aa2 100644 --- a/setup_common.py +++ b/setup_common.py @@ -22,7 +22,7 @@ def enabled(options, option): elif s in ("no", "false", "0", "n"): return False else: - raise ValueError("Unknown value {} for option {}".format(value, option)) + raise ValueError(f"Unknown value {value} for option {option}") def create_release_file(metadata): diff --git a/tests/capabilities.py b/tests/capabilities.py index 034e88da..1e695e9e 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -35,13 +35,13 @@ def tearDown(self): del self.cursor orphans = gc.collect() - self.failIf( + self.assertFalse( orphans, "%d orphaned objects found after deleting cursor" % orphans ) del self.connection orphans = gc.collect() - self.failIf( + self.assertFalse( orphans, "%d orphaned objects found after deleting connection" % orphans ) @@ -82,7 +82,7 @@ def create_table(self, columndefs): def check_data_integrity(self, columndefs, generator): # insert self.create_table(columndefs) - insert_statement = "INSERT INTO %s VALUES (%s)" % ( + insert_statement = "INSERT INTO {} VALUES ({})".format( self.table, ",".join(["%s"] * len(columndefs)), ) @@ -113,7 +113,7 @@ def generator(row, col): return ("%i" % (row % 10)) * 255 self.create_table(columndefs) - insert_statement = "INSERT INTO %s VALUES (%s)" % ( + insert_statement = "INSERT INTO {} VALUES ({})".format( self.table, ",".join(["%s"] * len(columndefs)), ) @@ -131,11 +131,11 @@ def generator(row, col): self.assertEqual(res[i][j], generator(i, j)) delete_statement = "delete from %s where col1=%%s" % self.table self.cursor.execute(delete_statement, (0,)) - self.cursor.execute("select col1 from %s where col1=%s" % (self.table, 0)) + self.cursor.execute(f"select col1 from {self.table} where col1=%s", (0,)) res = self.cursor.fetchall() self.assertFalse(res, "DELETE didn't work") self.connection.rollback() - self.cursor.execute("select col1 from %s where col1=%s" % (self.table, 0)) + self.cursor.execute(f"select col1 from {self.table} where col1=%s", (0,)) res = self.cursor.fetchall() self.assertTrue(len(res) == 1, "ROLLBACK didn't work") self.cursor.execute("drop table %s" % (self.table)) @@ -150,7 +150,7 @@ def generator(row, col): return ("%i" % (row % 10)) * ((255 - self.rows // 2) + row) self.create_table(columndefs) - insert_statement = "INSERT INTO %s VALUES (%s)" % ( + insert_statement = "INSERT INTO {} VALUES ({})".format( self.table, ",".join(["%s"] * len(columndefs)), ) diff --git a/tests/dbapi20.py b/tests/dbapi20.py index a88a0616..be0f6292 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -525,8 +525,7 @@ def _populate(self): tests. """ populate = [ - "insert into {}booze values ('{}')".format(self.table_prefix, s) - for s in self.samples + f"insert into {self.table_prefix}booze values ('{s}')" for s in self.samples ] return populate diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 91f0323e..80e21888 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -18,7 +18,7 @@ def teardown_function(function): c = _conns[0] cur = c.cursor() for t in _tables: - cur.execute("DROP TABLE {}".format(t)) + cur.execute(f"DROP TABLE {t}") cur.close() del _tables[:] diff --git a/tests/test_errors.py b/tests/test_errors.py index 3a9fecaa..fae28e81 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -18,7 +18,7 @@ def teardown_function(function): c = _conns[0] cur = c.cursor() for t in _tables: - cur.execute("DROP TABLE {}".format(t)) + cur.execute(f"DROP TABLE {t}") cur.close() del _tables[:] From d2c07e8a0e760025edef2b753fffacd2b75b75d0 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 14:40:56 +0900 Subject: [PATCH 322/396] Update workflows (#593) * Drop Python 3.7 * Use latest actions Fix #591 --- .github/workflows/django.yaml | 17 ++++------------- .github/workflows/tests.yaml | 21 ++++++--------------- metadata.cfg | 1 - requirements.txt | 5 +++++ 4 files changed, 15 insertions(+), 29 deletions(-) create mode 100644 requirements.txt diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml index 4e18374a..55497767 100644 --- a/.github/workflows/django.yaml +++ b/.github/workflows/django.yaml @@ -5,7 +5,7 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Start MySQL run: | @@ -13,29 +13,20 @@ jobs: mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" - - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-django-pip-1 - restore-keys: | - ${{ runner.os }}-pip- + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: # https://www.mail-archive.com/django-updates@googlegroups.com/msg209056.html python-version: "3.8" - - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - name: Install mysqlclient env: PIP_NO_PYTHON_VERSION_WARNING: 1 PIP_DISABLE_PIP_VERSION_CHECK: 1 run: | - pip install -U pytest pytest-cov tblib + pip install -r requirements.txt pip install . # pip install mysqlclient # Use stable version diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0b34ecb4..73681427 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -6,29 +6,20 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - name: Start MySQL run: | sudo systemctl start mysql.service mysql -uroot -proot -e "CREATE DATABASE mysqldb_test" - - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-1 - restore-keys: | - ${{ runner.os }}-pip- - - - uses: actions/checkout@v2 - with: - fetch-depth: 2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -37,7 +28,7 @@ jobs: PIP_NO_PYTHON_VERSION_WARNING: 1 PIP_DISABLE_PIP_VERSION_CHECK: 1 run: | - pip install -U coverage pytest pytest-cov + pip install -r requirements.txt python setup.py develop - name: Run tests @@ -46,4 +37,4 @@ jobs: run: | pytest --cov=MySQLdb tests - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 diff --git a/metadata.cfg b/metadata.cfg index 87ebc6c5..38deff56 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -21,7 +21,6 @@ classifiers: Programming Language :: C Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e2546870 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# This file is for GitHub Action +coverage +pytest +pytest-cov +tblib From 869fe107af08750ea839dfba16e3d58f7d611b61 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 17:25:18 +0900 Subject: [PATCH 323/396] Update Django test workflow (#594) Django 3.2 LTS will be supported until 2024-04. --- .github/workflows/django.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml index 55497767..6cf0ed0c 100644 --- a/.github/workflows/django.yaml +++ b/.github/workflows/django.yaml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-python@v4 with: # https://www.mail-archive.com/django-updates@googlegroups.com/msg209056.html - python-version: "3.8" + python-version: "3.11" - name: Install mysqlclient env: @@ -32,7 +32,7 @@ jobs: - name: Run Django test env: - DJANGO_VERSION: "2.2.24" + DJANGO_VERSION: "3.2.19" run: | sudo apt-get install libmemcached-dev wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz From 1f4fb4d98a016c23751c8b9b67ad840e5e79e4df Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 9 May 2023 23:35:52 +0900 Subject: [PATCH 324/396] CI: Update codeql build. (#595) --- .github/workflows/codeql.yml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 90a8e5b0..ddc6d6e2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,26 +17,22 @@ jobs: contents: read security-events: write - strategy: - fail-fast: false - matrix: - language: [ python, cpp ] - steps: - name: Checkout uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - queries: +security-and-quality + # with: + # languages: ${{ matrix.language }} + # queries: +security-and-quality + + # - name: Autobuild + # uses: github/codeql-action/autobuild@v2 - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - if: ${{ matrix.language == 'python' || matrix.language == 'cpp' }} + - name: Build + run: | + python setup.py build_ext -if - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{ matrix.language }}" From b7255d3aea843b7c039093e77418305b1f956c08 Mon Sep 17 00:00:00 2001 From: piglei Date: Wed, 10 May 2023 14:37:57 +0800 Subject: [PATCH 325/396] Improved exception handling when importing the module (#596) The current expection handling is too vague, and in certain circumstances, the error message may confuse the user. For example, if an error occurs while importing the "_mysql" module, the original error message is as follows: ``` File "MySQLdb/__init__.py", line 18, in from . import _mysql ImportError: /lib64/libstdc++.so.6: cannot allocate memory in static TLS block ``` But on the user side, he can only see the exception message like this: ``` /MySQLdb/__init__.py", line 24, in version_info, _mysql.version_info, _mysql.__file__ NameError: name '_mysql' is not defined ``` This PR fixes this issue by making the exception handling statements more precise. --- MySQLdb/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index b567363b..2851b9bc 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -13,12 +13,11 @@ MySQLdb.converters module. """ -try: - from MySQLdb.release import version_info - from . import _mysql +# Check if the version of _mysql matches the version of MySQLdb. +from MySQLdb.release import version_info +from . import _mysql - assert version_info == _mysql.version_info -except Exception: +if version_info != _mysql.version_info: raise ImportError( "this is MySQLdb version {}, but _mysql is version {!r}\n_mysql: {!r}".format( version_info, _mysql.version_info, _mysql.__file__ From cbd894c3b14267d75e22dadb6b186e0508394779 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 10 May 2023 17:15:22 +0900 Subject: [PATCH 326/396] CI: Fix django workflow (#597) --- .github/workflows/django.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml index 6cf0ed0c..b09d7823 100644 --- a/.github/workflows/django.yaml +++ b/.github/workflows/django.yaml @@ -12,6 +12,7 @@ jobs: sudo systemctl start mysql.service mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" + mysql -uroot -proot -e "CREATE DATABASE django_test; CREATE DATABASE django_other;" - uses: actions/checkout@v3 From 89c1e0f3c6353ac0cf5104f1a5cfe6dafeb28938 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 15 May 2023 13:22:35 +0900 Subject: [PATCH 327/396] Use pyproject.toml (#598) --- .gitignore | 1 - MANIFEST.in | 5 -- Makefile | 5 ++ MySQLdb/__init__.py | 9 +-- MySQLdb/release.py | 3 + metadata.cfg | 41 ---------- pyproject.toml | 48 ++++++++++++ setup.py | 177 ++++++++++++++++++++++++++++++++++++++++---- setup_common.py | 37 --------- setup_posix.py | 94 ----------------------- setup_windows.py | 64 ---------------- 11 files changed, 223 insertions(+), 261 deletions(-) create mode 100644 MySQLdb/release.py delete mode 100644 metadata.cfg create mode 100644 pyproject.toml delete mode 100644 setup_common.py delete mode 100644 setup_posix.py delete mode 100644 setup_windows.py diff --git a/.gitignore b/.gitignore index 42bbfb5d..1f081cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,4 @@ .tox/ build/ dist/ -MySQLdb/release.py .coverage diff --git a/MANIFEST.in b/MANIFEST.in index 07563caf..58a996de 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,12 +1,7 @@ recursive-include doc *.rst recursive-include tests *.py include doc/conf.py -include MANIFEST.in include HISTORY.rst include README.md include LICENSE -include metadata.cfg include site.cfg -include setup_common.py -include setup_posix.py -include setup_windows.py diff --git a/Makefile b/Makefile index 783d1919..850e296e 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,8 @@ clean: find . -name '*.pyc' -delete find . -name '__pycache__' -delete rm -rf build + +.PHONY: check +check: + ruff . + black *.py MySQLdb diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 2851b9bc..153bbdfe 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -13,15 +13,14 @@ MySQLdb.converters module. """ -# Check if the version of _mysql matches the version of MySQLdb. -from MySQLdb.release import version_info +from .release import version_info from . import _mysql if version_info != _mysql.version_info: raise ImportError( - "this is MySQLdb version {}, but _mysql is version {!r}\n_mysql: {!r}".format( - version_info, _mysql.version_info, _mysql.__file__ - ) + f"this is MySQLdb version {version_info}, " + f"but _mysql is version {_mysql.version_info!r}\n" + f"_mysql: {_mysql.__file__!r}" ) diff --git a/MySQLdb/release.py b/MySQLdb/release.py new file mode 100644 index 00000000..55359628 --- /dev/null +++ b/MySQLdb/release.py @@ -0,0 +1,3 @@ +__author__ = "Inada Naoki " +version_info = (2, 2, 0, "dev", 0) +__version__ = "2.2.0.dev0" diff --git a/metadata.cfg b/metadata.cfg deleted file mode 100644 index 38deff56..00000000 --- a/metadata.cfg +++ /dev/null @@ -1,41 +0,0 @@ -[metadata] -name: mysqlclient -version: 2.2.0dev0 -version_info: (2,2,0,'dev',0) -description: Python interface to MySQL -author: Inada Naoki -author_email: songofacandy@gmail.com -license: GPL -platforms: ALL -url: https://github.com/PyMySQL/mysqlclient -classifiers: - Development Status :: 5 - Production/Stable - Environment :: Other Environment - License :: OSI Approved :: GNU General Public License (GPL) - Operating System :: MacOS :: MacOS X - Operating System :: Microsoft :: Windows :: Windows NT/2000 - Operating System :: OS Independent - Operating System :: POSIX - Operating System :: POSIX :: Linux - Operating System :: Unix - Programming Language :: C - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Topic :: Database - Topic :: Database :: Database Engines/Servers -py_modules: - MySQLdb._exceptions - MySQLdb.connections - MySQLdb.converters - MySQLdb.cursors - MySQLdb.release - MySQLdb.times - MySQLdb.constants.CLIENT - MySQLdb.constants.CR - MySQLdb.constants.ER - MySQLdb.constants.FIELD_TYPE - MySQLdb.constants.FLAG diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..907bf55f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[project] +name = "mysqlclient" +# version = "2.2.0dev0" +description = "Python interface to MySQL" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + {name = "Inada Naoki", email = "songofacandy@gmail.com"} +] +license = {text = "GNU General Public License v2 (GPLv2)"} +keywords = ["MySQL"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Other Environment", + "License :: OSI Approved :: GNU General Public License (GPL)", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows :: Windows NT/2000", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: POSIX :: Linux", + "Operating System :: Unix", + "Programming Language :: C", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Database", + "Topic :: Database :: Database Engines/Servers", +] +dynamic = ["version"] + +[project.urls] +Project = "https://github.com/PyMySQL/mysqlclient" +Documentation = "https://mysqlclient.readthedocs.io/" + +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +namespaces = false +include = ["MySQLdb*"] +exclude = ["tests*", "pymysql.tests*"] + +[tool.setuptools.dynamic] +version = {attr = "MySQLdb.release.__version__"} diff --git a/setup.py b/setup.py index aa6c34fb..368617ef 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,171 @@ #!/usr/bin/env python - import os +import subprocess +import sys import setuptools +from configparser import ConfigParser + + +release_info = {} +with open("MySQLdb/release.py", encoding="utf-8") as f: + exec(f.read(), None, release_info) + + +def find_package_name(): + """Get available pkg-config package name""" + packages = ["mysqlclient", "mariadb"] + for pkg in packages: + try: + cmd = f"pkg-config --exists {pkg}" + print(f"Trying {cmd}") + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as err: + print(err) + else: + return pkg + raise Exception( + "Can not find valid pkg-config name.\n" + "Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually" + ) + + +def get_config_posix(options=None): + # allow a command-line option to override the base config file to permit + # a static build to be created via requirements.txt + # TODO: find a better way for + static = False + if "--static" in sys.argv: + static = True + sys.argv.remove("--static") + + ldflags = os.environ.get("MYSQLCLIENT_LDFLAGS") + cflags = os.environ.get("MYSQLCLIENT_CFLAGS") + + pkg_name = None + static_opt = " --static" if static else "" + if not (cflags and ldflags): + pkg_name = find_package_name() + if not cflags: + cflags = subprocess.check_output( + f"pkg-config{static_opt} --cflags {pkg_name}", encoding="utf-8", shell=True + ) + if not ldflags: + ldflags = subprocess.check_output( + f"pkg-config{static_opt} --libs {pkg_name}", encoding="utf-8", shell=True + ) + + cflags = cflags.split() + for f in cflags: + if f.startswith("-std="): + break + else: + cflags += ["-std=c99"] + + ldflags = ldflags.split() + + define_macros = [ + ("version_info", release_info["version_info"]), + ("__version__", release_info["__version__"]), + ] + + ext_options = dict( + extra_compile_args=cflags, + extra_link_args=ldflags, + define_macros=define_macros, + ) + # newer versions of gcc require libstdc++ if doing a static build + if static: + ext_options["language"] = "c++" + + print("Options for building extention module:") + for k, v in ext_options.items(): + print(f" {k}: {v}") + + return ext_options + + +def get_config_win32(options): + client = "mariadbclient" + connector = os.environ.get("MYSQLCLIENT_CONNECTOR", options.get("connector")) + if not connector: + connector = os.path.join( + os.environ["ProgramFiles"], "MariaDB", "MariaDB Connector C" + ) + + extra_objects = [] + + library_dirs = [ + os.path.join(connector, "lib", "mariadb"), + os.path.join(connector, "lib"), + ] + libraries = [ + "kernel32", + "advapi32", + "wsock32", + "shlwapi", + "Ws2_32", + "crypt32", + "secur32", + "bcrypt", + client, + ] + include_dirs = [ + os.path.join(connector, "include", "mariadb"), + os.path.join(connector, "include"), + ] + + extra_link_args = ["/MANIFEST"] + + define_macros = [ + ("version_info", release_info["version_info"]), + ("__version__", release_info["__version__"]), + ] + + ext_options = dict( + library_dirs=library_dirs, + libraries=libraries, + extra_link_args=extra_link_args, + include_dirs=include_dirs, + extra_objects=extra_objects, + define_macros=define_macros, + ) + return ext_options + + +def enabled(options, option): + value = options[option] + s = value.lower() + if s in ("yes", "true", "1", "y"): + return True + elif s in ("no", "false", "0", "n"): + return False + else: + raise ValueError(f"Unknown value {value} for option {option}") + + +def get_options(): + config = ConfigParser() + config.read(["site.cfg"]) + options = dict(config.items("options")) + options["static"] = enabled(options, "static") + return options + -if os.name == "posix": - from setup_posix import get_config -else: # assume windows - from setup_windows import get_config +if sys.platform == "win32": + ext_options = get_config_win32(get_options()) +else: + ext_options = get_config_posix(get_options()) -with open("README.md", encoding="utf-8") as f: - readme = f.read() +print("# Extention options") +for k, v in ext_options.items(): + print(f" {k}: {v}") -metadata, options = get_config() -metadata["ext_modules"] = [ - setuptools.Extension("MySQLdb._mysql", sources=["MySQLdb/_mysql.c"], **options) +ext_modules = [ + setuptools.Extension( + "MySQLdb._mysql", + sources=["MySQLdb/_mysql.c"], + **ext_options, + ) ] -metadata["long_description"] = readme -metadata["long_description_content_type"] = "text/markdown" -metadata["python_requires"] = ">=3.7" -setuptools.setup(**metadata) +setuptools.setup(ext_modules=ext_modules) diff --git a/setup_common.py b/setup_common.py deleted file mode 100644 index 53869aa2..00000000 --- a/setup_common.py +++ /dev/null @@ -1,37 +0,0 @@ -from configparser import ConfigParser - - -def get_metadata_and_options(): - config = ConfigParser() - config.read(["metadata.cfg", "site.cfg"]) - - metadata = dict(config.items("metadata")) - options = dict(config.items("options")) - - metadata["py_modules"] = list(filter(None, metadata["py_modules"].split("\n"))) - metadata["classifiers"] = list(filter(None, metadata["classifiers"].split("\n"))) - - return metadata, options - - -def enabled(options, option): - value = options[option] - s = value.lower() - if s in ("yes", "true", "1", "y"): - return True - elif s in ("no", "false", "0", "n"): - return False - else: - raise ValueError(f"Unknown value {value} for option {option}") - - -def create_release_file(metadata): - with open("MySQLdb/release.py", "w", encoding="utf-8") as rel: - rel.write( - """ -__author__ = "%(author)s <%(author_email)s>" -version_info = %(version_info)s -__version__ = "%(version)s" -""" - % metadata - ) diff --git a/setup_posix.py b/setup_posix.py deleted file mode 100644 index a03dd22c..00000000 --- a/setup_posix.py +++ /dev/null @@ -1,94 +0,0 @@ -import os -import sys -import subprocess - - -def find_package_name(): - """Get available pkg-config package name""" - packages = ["mysqlclient", "mariadb"] - for pkg in packages: - try: - cmd = f"pkg-config --exists {pkg}" - print(f"Trying {cmd}") - subprocess.check_call(cmd, shell=True) - except subprocess.CalledProcessError as err: - print(err) - else: - return pkg - raise Exception("Can not find valid pkg-config") - - -def get_config(): - from setup_common import get_metadata_and_options, enabled, create_release_file - - metadata, options = get_metadata_and_options() - - static = enabled(options, "static") - # allow a command-line option to override the base config file to permit - # a static build to be created via requirements.txt - # - if "--static" in sys.argv: - static = True - sys.argv.remove("--static") - - ldflags = os.environ.get("MYSQLCLIENT_LDFLAGS") - cflags = os.environ.get("MYSQLCLIENT_CFLAGS") - - pkg_name = None - static_opt = " --static" if static else "" - if not (cflags and ldflags): - pkg_name = find_package_name() - if not cflags: - cflags = subprocess.check_output( - f"pkg-config{static_opt} --cflags {pkg_name}", encoding="utf-8", shell=True - ) - if not ldflags: - ldflags = subprocess.check_output( - f"pkg-config{static_opt} --libs {pkg_name}", encoding="utf-8", shell=True - ) - - cflags = cflags.split() - for f in cflags: - if f.startswith("-std="): - break - else: - cflags += ["-std=c99"] - - ldflags = ldflags.split() - - define_macros = [ - ("version_info", metadata["version_info"]), - ("__version__", metadata["version"]), - ] - - # print(f"{cflags = }") - # print(f"{ldflags = }") - # print(f"{define_macros = }") - - ext_options = dict( - extra_compile_args=cflags, - extra_link_args=ldflags, - define_macros=define_macros, - ) - # newer versions of gcc require libstdc++ if doing a static build - if static: - ext_options["language"] = "c++" - - print("Options for building extention module:") - for k, v in ext_options.items(): - print(f" {k}: {v}") - - create_release_file(metadata) - del metadata["version_info"] - - return metadata, ext_options - - -if __name__ == "__main__": - from pprint import pprint - - metadata, config = get_config() - print("# Metadata") - pprint(metadata, sort_dicts=False, compact=True) - print("\n# Extention options") - pprint(config, sort_dicts=False, compact=True) diff --git a/setup_windows.py b/setup_windows.py deleted file mode 100644 index 5d8d7158..00000000 --- a/setup_windows.py +++ /dev/null @@ -1,64 +0,0 @@ -import os - - -def get_config(): - from setup_common import get_metadata_and_options, create_release_file - - metadata, options = get_metadata_and_options() - - client = "mariadbclient" - connector = os.environ.get("MYSQLCLIENT_CONNECTOR", options.get("connector")) - if not connector: - connector = os.path.join( - os.environ["ProgramFiles"], "MariaDB", "MariaDB Connector C" - ) - - extra_objects = [] - - library_dirs = [ - os.path.join(connector, "lib", "mariadb"), - os.path.join(connector, "lib"), - ] - libraries = [ - "kernel32", - "advapi32", - "wsock32", - "shlwapi", - "Ws2_32", - "crypt32", - "secur32", - "bcrypt", - client, - ] - include_dirs = [ - os.path.join(connector, "include", "mariadb"), - os.path.join(connector, "include"), - ] - - extra_link_args = ["/MANIFEST"] - - define_macros = [ - ("version_info", metadata["version_info"]), - ("__version__", metadata["version"]), - ] - create_release_file(metadata) - del metadata["version_info"] - ext_options = dict( - library_dirs=library_dirs, - libraries=libraries, - extra_link_args=extra_link_args, - include_dirs=include_dirs, - extra_objects=extra_objects, - define_macros=define_macros, - ) - return metadata, ext_options - - -if __name__ == "__main__": - from pprint import pprint - - metadata, config = get_config() - print("# Metadata") - pprint(metadata) - print("\n# Extention options") - pprint(config) From 9953e509c4f712d45a4f5060ddbd65c3026bea63 Mon Sep 17 00:00:00 2001 From: Steve Teahan Date: Mon, 15 May 2023 01:17:21 -0400 Subject: [PATCH 328/396] Add Cursor.mogrify(). (#477) Implements #476 Co-authored-by: Inada Naoki --- MySQLdb/cursors.py | 27 ++++++++++++++++++++++++--- tests/test_cursor.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index f8a48640..fdf52c0b 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -182,6 +182,15 @@ def execute(self, query, args=None): """ while self.nextset(): pass + + mogrified_query = self._mogrify(query, args) + + assert isinstance(mogrified_query, (bytes, bytearray)) + res = self._query(mogrified_query) + return res + + def _mogrify(self, query, args=None): + """Return query after binding args.""" db = self._get_db() if isinstance(query, str): @@ -202,9 +211,21 @@ def execute(self, query, args=None): except TypeError as m: raise ProgrammingError(str(m)) - assert isinstance(query, (bytes, bytearray)) - res = self._query(query) - return res + return query + + def mogrify(self, query, args=None): + """Return query after binding args. + + query -- string, query to mogrify + args -- optional sequence or mapping, parameters to use with query. + + Note: If args is a sequence, then %s must be used as the + parameter placeholder in the query. If a mapping is used, + %(key)s must be used as the placeholder. + + Returns string representing query that would be executed by the server + """ + return self._mogrify(query, args).decode(self._get_db().encoding) def executemany(self, query, args): # type: (str, list) -> int diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 80e21888..c681b63b 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -150,3 +150,39 @@ def test_dictcursor(): names2 = sorted(rows[1]) for a, b in zip(names1, names2): assert a is b + + +def test_mogrify_without_args(): + conn = connect() + cursor = conn.cursor() + + query = "SELECT VERSION()" + mogrified_query = cursor.mogrify(query) + cursor.execute(query) + + assert mogrified_query == query + assert mogrified_query == cursor._executed.decode() + + +def test_mogrify_with_tuple_args(): + conn = connect() + cursor = conn.cursor() + + query_with_args = "SELECT %s, %s", (1, 2) + mogrified_query = cursor.mogrify(*query_with_args) + cursor.execute(*query_with_args) + + assert mogrified_query == "SELECT 1, 2" + assert mogrified_query == cursor._executed.decode() + + +def test_mogrify_with_dict_args(): + conn = connect() + cursor = conn.cursor() + + query_with_args = "SELECT %(a)s, %(b)s", {"a": 1, "b": 2} + mogrified_query = cursor.mogrify(*query_with_args) + cursor.execute(*query_with_args) + + assert mogrified_query == "SELECT 1, 2" + assert mogrified_query == cursor._executed.decode() From abb139bc5471ad59be214d20a76323c0f97a2193 Mon Sep 17 00:00:00 2001 From: Teodor Moroz Date: Mon, 15 May 2023 12:09:04 +0300 Subject: [PATCH 329/396] Support ssl_mode setting with mariadb client (#475) According to https://mariadb.com/kb/en/mysql_optionsv/ MariaDB supports TLS enforcing in its own way. So the idea behind this PR is to keep the same interface for MariaDB based clients, but behind the scenes handle it accordingly.(MariaDB gets its own args set, instead of ssl_mode dict supported by MySQL). Co-authored-by: Teodor Moroz Co-authored-by: Inada Naoki --- MySQLdb/_mysql.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 6c04ec99..4463f627 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -380,7 +380,14 @@ static int _mysql_ResultObject_clear(_mysql_ResultObject *self) return 0; } -#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE +enum { + SSLMODE_DISABLED = 1, + SSLMODE_PREFERRED = 2, + SSLMODE_REQUIRED = 3, + SSLMODE_VERIFY_CA = 4, + SSLMODE_VERIFY_IDENTITY = 5 +}; + static int _get_ssl_mode_num(char *ssl_mode) { @@ -395,7 +402,6 @@ _get_ssl_mode_num(char *ssl_mode) } return -1; } -#endif static int _mysql_ConnectionObject_Initialize( @@ -429,6 +435,7 @@ _mysql_ConnectionObject_Initialize( int read_timeout = 0; int write_timeout = 0; int compress = -1, named_pipe = -1, local_infile = -1; + int ssl_mode_num = SSLMODE_DISABLED; char *init_command=NULL, *read_default_file=NULL, *read_default_group=NULL, @@ -469,15 +476,10 @@ _mysql_ConnectionObject_Initialize( _stringsuck(cipher, value, ssl); } if (ssl_mode) { -#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE - if (_get_ssl_mode_num(ssl_mode) <= 0) { + if ((ssl_mode_num = _get_ssl_mode_num(ssl_mode)) <= 0) { PyErr_SetString(_mysql_NotSupportedError, "Unknown ssl_mode specification"); return -1; } -#else - PyErr_SetString(_mysql_NotSupportedError, "MySQL client library does not support ssl_mode specification"); - return -1; -#endif } conn = mysql_init(&(self->connection)); @@ -487,6 +489,7 @@ _mysql_ConnectionObject_Initialize( } Py_BEGIN_ALLOW_THREADS ; self->open = 1; + if (connect_timeout) { unsigned int timeout = connect_timeout; mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, @@ -521,12 +524,23 @@ _mysql_ConnectionObject_Initialize( if (ssl) { mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); } -#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE if (ssl_mode) { - int ssl_mode_num = _get_ssl_mode_num(ssl_mode); +#ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE mysql_options(&(self->connection), MYSQL_OPT_SSL_MODE, &ssl_mode_num); - } +#else + // MariaDB doesn't support MYSQL_OPT_SSL_MODE. + // See https://github.com/PyMySQL/mysqlclient/issues/474 + // TODO: Does MariaDB supports PREFERRED and VERIFY_CA? + // We support only two levels for now. + if (sslmode_num >= SSLMODE_REQUIRED) { + mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&enforce_tls); + } + if (sslmode_num >= SSLMODE_VERIFY_CA) { + mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&enforce_tls); + } #endif + } + if (charset) { mysql_options(&(self->connection), MYSQL_SET_CHARSET_NAME, charset); } From 0220f427a9102c0c293ba2d91f8cad866109e406 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 15 May 2023 23:50:10 +0900 Subject: [PATCH 330/396] Use src-layout. (#600) --- .github/workflows/tests.yaml | 2 +- Makefile | 6 +++--- codecov.yml | 2 +- pyproject.toml | 5 ++++- setup.py | 4 ++-- {MySQLdb => src/MySQLdb}/__init__.py | 0 {MySQLdb => src/MySQLdb}/_exceptions.py | 0 {MySQLdb => src/MySQLdb}/_mysql.c | 0 {MySQLdb => src/MySQLdb}/connections.py | 0 {MySQLdb => src/MySQLdb}/constants/CLIENT.py | 0 {MySQLdb => src/MySQLdb}/constants/CR.py | 0 {MySQLdb => src/MySQLdb}/constants/ER.py | 0 {MySQLdb => src/MySQLdb}/constants/FIELD_TYPE.py | 0 {MySQLdb => src/MySQLdb}/constants/FLAG.py | 0 {MySQLdb => src/MySQLdb}/constants/__init__.py | 0 {MySQLdb => src/MySQLdb}/converters.py | 0 {MySQLdb => src/MySQLdb}/cursors.py | 0 {MySQLdb => src/MySQLdb}/release.py | 0 {MySQLdb => src/MySQLdb}/times.py | 0 19 files changed, 11 insertions(+), 8 deletions(-) rename {MySQLdb => src/MySQLdb}/__init__.py (100%) rename {MySQLdb => src/MySQLdb}/_exceptions.py (100%) rename {MySQLdb => src/MySQLdb}/_mysql.c (100%) rename {MySQLdb => src/MySQLdb}/connections.py (100%) rename {MySQLdb => src/MySQLdb}/constants/CLIENT.py (100%) rename {MySQLdb => src/MySQLdb}/constants/CR.py (100%) rename {MySQLdb => src/MySQLdb}/constants/ER.py (100%) rename {MySQLdb => src/MySQLdb}/constants/FIELD_TYPE.py (100%) rename {MySQLdb => src/MySQLdb}/constants/FLAG.py (100%) rename {MySQLdb => src/MySQLdb}/constants/__init__.py (100%) rename {MySQLdb => src/MySQLdb}/converters.py (100%) rename {MySQLdb => src/MySQLdb}/cursors.py (100%) rename {MySQLdb => src/MySQLdb}/release.py (100%) rename {MySQLdb => src/MySQLdb}/times.py (100%) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 73681427..31ee34d3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,7 +29,7 @@ jobs: PIP_DISABLE_PIP_VERSION_CHECK: 1 run: | pip install -r requirements.txt - python setup.py develop + pip install . - name: Run tests env: diff --git a/Makefile b/Makefile index 850e296e..f0e94c3b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: build build: - python3 setup.py build_ext -if + python setup.py build_ext -if .PHONY: doc doc: @@ -10,7 +10,7 @@ doc: .PHONY: clean clean: - python3 setup.py clean + python setup.py clean find . -name '*.pyc' -delete find . -name '__pycache__' -delete rm -rf build @@ -18,4 +18,4 @@ clean: .PHONY: check check: ruff . - black *.py MySQLdb + black *.py src diff --git a/codecov.yml b/codecov.yml index 174a4994..014486d2 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,2 @@ ignore: - - "MySQLdb/constants/*" + - "src/MySQLdb/constants/*" diff --git a/pyproject.toml b/pyproject.toml index 907bf55f..3bfd1f6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,10 +39,13 @@ Documentation = "https://mysqlclient.readthedocs.io/" requires = ["setuptools>=61"] build-backend = "setuptools.build_meta" +[tool.setuptools] +package-dir = {"" = "src"} + [tool.setuptools.packages.find] namespaces = false +where = ["src"] include = ["MySQLdb*"] -exclude = ["tests*", "pymysql.tests*"] [tool.setuptools.dynamic] version = {attr = "MySQLdb.release.__version__"} diff --git a/setup.py b/setup.py index 368617ef..5594df54 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ release_info = {} -with open("MySQLdb/release.py", encoding="utf-8") as f: +with open("src/MySQLdb/release.py", encoding="utf-8") as f: exec(f.read(), None, release_info) @@ -164,7 +164,7 @@ def get_options(): ext_modules = [ setuptools.Extension( "MySQLdb._mysql", - sources=["MySQLdb/_mysql.c"], + sources=["src/MySQLdb/_mysql.c"], **ext_options, ) ] diff --git a/MySQLdb/__init__.py b/src/MySQLdb/__init__.py similarity index 100% rename from MySQLdb/__init__.py rename to src/MySQLdb/__init__.py diff --git a/MySQLdb/_exceptions.py b/src/MySQLdb/_exceptions.py similarity index 100% rename from MySQLdb/_exceptions.py rename to src/MySQLdb/_exceptions.py diff --git a/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c similarity index 100% rename from MySQLdb/_mysql.c rename to src/MySQLdb/_mysql.c diff --git a/MySQLdb/connections.py b/src/MySQLdb/connections.py similarity index 100% rename from MySQLdb/connections.py rename to src/MySQLdb/connections.py diff --git a/MySQLdb/constants/CLIENT.py b/src/MySQLdb/constants/CLIENT.py similarity index 100% rename from MySQLdb/constants/CLIENT.py rename to src/MySQLdb/constants/CLIENT.py diff --git a/MySQLdb/constants/CR.py b/src/MySQLdb/constants/CR.py similarity index 100% rename from MySQLdb/constants/CR.py rename to src/MySQLdb/constants/CR.py diff --git a/MySQLdb/constants/ER.py b/src/MySQLdb/constants/ER.py similarity index 100% rename from MySQLdb/constants/ER.py rename to src/MySQLdb/constants/ER.py diff --git a/MySQLdb/constants/FIELD_TYPE.py b/src/MySQLdb/constants/FIELD_TYPE.py similarity index 100% rename from MySQLdb/constants/FIELD_TYPE.py rename to src/MySQLdb/constants/FIELD_TYPE.py diff --git a/MySQLdb/constants/FLAG.py b/src/MySQLdb/constants/FLAG.py similarity index 100% rename from MySQLdb/constants/FLAG.py rename to src/MySQLdb/constants/FLAG.py diff --git a/MySQLdb/constants/__init__.py b/src/MySQLdb/constants/__init__.py similarity index 100% rename from MySQLdb/constants/__init__.py rename to src/MySQLdb/constants/__init__.py diff --git a/MySQLdb/converters.py b/src/MySQLdb/converters.py similarity index 100% rename from MySQLdb/converters.py rename to src/MySQLdb/converters.py diff --git a/MySQLdb/cursors.py b/src/MySQLdb/cursors.py similarity index 100% rename from MySQLdb/cursors.py rename to src/MySQLdb/cursors.py diff --git a/MySQLdb/release.py b/src/MySQLdb/release.py similarity index 100% rename from MySQLdb/release.py rename to src/MySQLdb/release.py diff --git a/MySQLdb/times.py b/src/MySQLdb/times.py similarity index 100% rename from MySQLdb/times.py rename to src/MySQLdb/times.py From a2e970698fcc5af401092016c474260c2f267655 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 17 May 2023 02:08:52 +0900 Subject: [PATCH 331/396] Discard results without converting them into Python objects. (#601) Fixes #560. --- src/MySQLdb/_mysql.c | 69 ++++++++++++++++++++++++++++++++++++++++++ src/MySQLdb/cursors.py | 24 ++++++++++++--- tests/test_cursor.py | 38 ++++++++++++++++++++++- 3 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 4463f627..b8a19d26 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -1484,6 +1484,26 @@ _mysql_ResultObject_fetch_row( return NULL; } +static const char _mysql_ResultObject_discard__doc__[] = +"discard() -- Discard remaining rows in the resultset."; + +static PyObject * +_mysql_ResultObject_discard( + _mysql_ResultObject *self, + PyObject *noargs) +{ + check_result_connection(self); + + MYSQL_ROW row; + while (NULL != (row = mysql_fetch_row(self->result))) { + // do nothing + } + if (mysql_errno(self->conn)) { + return _mysql_Exception(self->conn); + } + Py_RETURN_NONE; +} + static char _mysql_ConnectionObject_change_user__doc__[] = "Changes the user and causes the database specified by db to\n\ become the default (current) database on the connection\n\ @@ -2081,6 +2101,43 @@ _mysql_ConnectionObject_use_result( return result; } +static const char _mysql_ConnectionObject_discard_result__doc__[] = +"Discard current result set.\n\n" +"This function can be called instead of use_result() or store_result(). Non-standard."; + +static PyObject * +_mysql_ConnectionObject_discard_result( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + MYSQL *conn = &(self->connection); + + Py_BEGIN_ALLOW_THREADS; + + MYSQL_RES *res = mysql_use_result(conn); + if (res == NULL) { + Py_BLOCK_THREADS; + if (mysql_errno(conn) != 0) { + // fprintf(stderr, "mysql_use_result failed: %s\n", mysql_error(conn)); + return _mysql_Exception(self); + } + Py_RETURN_NONE; + } + + MYSQL_ROW row; + while (NULL != (row = mysql_fetch_row(res))) { + // do nothing. + } + mysql_free_result(res); + Py_END_ALLOW_THREADS; + if (mysql_errno(conn)) { + // fprintf(stderr, "mysql_free_result failed: %s\n", mysql_error(conn)); + return _mysql_Exception(self); + } + Py_RETURN_NONE; +} + static void _mysql_ConnectionObject_dealloc( _mysql_ConnectionObject *self) @@ -2376,6 +2433,12 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { METH_NOARGS, _mysql_ConnectionObject_use_result__doc__ }, + { + "discard_result", + (PyCFunction)_mysql_ConnectionObject_discard_result, + METH_NOARGS, + _mysql_ConnectionObject_discard_result__doc__ + }, {NULL, NULL} /* sentinel */ }; @@ -2437,6 +2500,12 @@ static PyMethodDef _mysql_ResultObject_methods[] = { METH_VARARGS | METH_KEYWORDS, _mysql_ResultObject_fetch_row__doc__ }, + { + "discard", + (PyCFunction)_mysql_ResultObject_discard, + METH_NOARGS, + _mysql_ResultObject_discard__doc__ + }, { "field_flags", (PyCFunction)_mysql_ResultObject_field_flags, diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index fdf52c0b..d3b2947b 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -75,13 +75,30 @@ def __init__(self, connection): self.rownumber = None self._rows = None + def _discard(self): + self.description = None + self.description_flags = None + self.rowcount = -1 + self.lastrowid = None + self._rows = None + self.rownumber = None + + if self._result: + self._result.discard() + self._result = None + + con = self.connection + if con is None: + return + while con.next_result() == 0: # -1 means no more data. + con.discard_result() + def close(self): """Close the cursor. No further queries will be possible.""" try: if self.connection is None: return - while self.nextset(): - pass + self._discard() finally: self.connection = None self._result = None @@ -180,8 +197,7 @@ def execute(self, query, args=None): Returns integer represents rows affected, if any """ - while self.nextset(): - pass + self._discard() mogrified_query = self._mogrify(query, args) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index c681b63b..5cb98910 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,4 +1,4 @@ -# import pytest +import pytest import MySQLdb.cursors from configdb import connection_factory @@ -186,3 +186,39 @@ def test_mogrify_with_dict_args(): assert mogrified_query == "SELECT 1, 2" assert mogrified_query == cursor._executed.decode() + + +# Test that cursor can be used without reading whole resultset. +@pytest.mark.parametrize("Cursor", [MySQLdb.cursors.Cursor, MySQLdb.cursors.SSCursor]) +def test_cursor_discard_result(Cursor): + conn = connect() + cursor = conn.cursor(Cursor) + + cursor.execute( + """\ +CREATE TABLE test_cursor_discard_result ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + data VARCHAR(100) +)""" + ) + _tables.append("test_cursor_discard_result") + + cursor.executemany( + "INSERT INTO test_cursor_discard_result (id, data) VALUES (%s, %s)", + [(i, f"row {i}") for i in range(1, 101)], + ) + + cursor.execute( + """\ +SELECT * FROM test_cursor_discard_result WHERE id <= 10; +SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 11 AND 20; +SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 21 AND 30; +""" + ) + cursor.nextset() + assert cursor.fetchone() == (11, "row 11") + + cursor.execute( + "SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 31 AND 40" + ) + assert cursor.fetchone() == (31, "row 31") From 3517eb77b73613db308c39c8b3af0e5aa51b8a2a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 17 May 2023 10:58:18 +0900 Subject: [PATCH 332/396] Fix sphinx warnings (#602) Fix #539 --- doc/MySQLdb.rst | 31 +++++++++++++------------------ doc/conf.py | 7 +++++++ src/MySQLdb/cursors.py | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/doc/MySQLdb.rst b/doc/MySQLdb.rst index 134a40b6..5e6791d5 100644 --- a/doc/MySQLdb.rst +++ b/doc/MySQLdb.rst @@ -9,53 +9,48 @@ MySQLdb Package :undoc-members: :show-inheritance: -:mod:`connections` Module -------------------------- +:mod:`MySQLdb.connections` Module +--------------------------------- .. automodule:: MySQLdb.connections :members: Connection :undoc-members: - :show-inheritance: -:mod:`converters` Module ------------------------- +:mod:`MySQLdb.converters` Module +-------------------------------- .. automodule:: MySQLdb.converters :members: :undoc-members: - :show-inheritance: -:mod:`cursors` Module ---------------------- +:mod:`MySQLdb.cursors` Module +----------------------------- .. automodule:: MySQLdb.cursors - :members: Cursor + :members: :undoc-members: :show-inheritance: -:mod:`times` Module -------------------- +:mod:`MySQLdb.times` Module +--------------------------- .. automodule:: MySQLdb.times :members: :undoc-members: - :show-inheritance: -:mod:`_mysql` Module --------------------- +:mod:`MySQLdb._mysql` Module +---------------------------- .. automodule:: MySQLdb._mysql :members: :undoc-members: - :show-inheritance: -:mod:`_exceptions` Module -------------------------- +:mod:`MySQLdb._exceptions` Module +--------------------------------- .. automodule:: MySQLdb._exceptions :members: :undoc-members: - :show-inheritance: Subpackages diff --git a/doc/conf.py b/doc/conf.py index 5d8cd1a0..3e919822 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,6 +22,13 @@ # -- General configuration ----------------------------------------------------- +nitpick_ignore = [ + ("py:class", "datetime.date"), + ("py:class", "datetime.time"), + ("py:class", "datetime.datetime"), +] + + # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = "1.0" diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index d3b2947b..7851359f 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -8,7 +8,7 @@ from ._exceptions import ProgrammingError -#: Regular expression for :meth:`Cursor.executemany`. +#: Regular expression for ``Cursor.executemany```. #: executemany only supports simple bulk insert. #: You can use it to load large dataset. RE_INSERT_VALUES = re.compile( From 3d6b8c9b7c69c8d7f23def2992835d8b93d67a53 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 17:19:10 +0900 Subject: [PATCH 333/396] Release GIL during result.discard() (#604) --- src/MySQLdb/_mysql.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index b8a19d26..b030af16 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -487,7 +487,6 @@ _mysql_ConnectionObject_Initialize( PyErr_SetNone(PyExc_MemoryError); return -1; } - Py_BEGIN_ALLOW_THREADS ; self->open = 1; if (connect_timeout) { @@ -548,10 +547,10 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_DEFAULT_AUTH, auth_plugin); } + Py_BEGIN_ALLOW_THREADS conn = mysql_real_connect(&(self->connection), host, user, passwd, db, port, unix_socket, client_flag); - - Py_END_ALLOW_THREADS ; + Py_END_ALLOW_THREADS if (ssl) { int i; @@ -1403,9 +1402,9 @@ _mysql__fetch_row( if (!self->use) row = mysql_fetch_row(self->result); else { - Py_BEGIN_ALLOW_THREADS; + Py_BEGIN_ALLOW_THREADS row = mysql_fetch_row(self->result); - Py_END_ALLOW_THREADS; + Py_END_ALLOW_THREADS } if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { _mysql_Exception((_mysql_ConnectionObject *)self->conn); @@ -1495,9 +1494,11 @@ _mysql_ResultObject_discard( check_result_connection(self); MYSQL_ROW row; + Py_BEGIN_ALLOW_THREADS while (NULL != (row = mysql_fetch_row(self->result))) { // do nothing } + Py_END_ALLOW_THREADS if (mysql_errno(self->conn)) { return _mysql_Exception(self->conn); } @@ -1747,9 +1748,7 @@ _mysql_ConnectionObject_insert_id( { my_ulonglong r; check_connection(self); - Py_BEGIN_ALLOW_THREADS r = mysql_insert_id(&(self->connection)); - Py_END_ALLOW_THREADS return PyLong_FromUnsignedLongLong(r); } @@ -2058,9 +2057,7 @@ _mysql_ConnectionObject_thread_id( { unsigned long pid; check_connection(self); - Py_BEGIN_ALLOW_THREADS pid = mysql_thread_id(&(self->connection)); - Py_END_ALLOW_THREADS return PyLong_FromLong((long)pid); } From 62f0645376ca71c39c90ea6e0d360a663e4f13a1 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 17:52:38 +0900 Subject: [PATCH 334/396] Fix executemany with binary prefix (#605) Fix #494 --- src/MySQLdb/cursors.py | 34 ++-------------------------------- tests/default.cnf | 5 +++-- tests/test_cursor.py | 23 ++++++++++++++++++++++- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index 7851359f..785fa9a1 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -110,34 +110,6 @@ def __exit__(self, *exc_info): del exc_info self.close() - def _escape_args(self, args, conn): - encoding = conn.encoding - literal = conn.literal - - def ensure_bytes(x): - if isinstance(x, str): - return x.encode(encoding) - elif isinstance(x, tuple): - return tuple(map(ensure_bytes, x)) - elif isinstance(x, list): - return list(map(ensure_bytes, x)) - return x - - if isinstance(args, (tuple, list)): - ret = tuple(literal(ensure_bytes(arg)) for arg in args) - elif isinstance(args, dict): - ret = { - ensure_bytes(key): literal(ensure_bytes(val)) - for (key, val) in args.items() - } - else: - # If it's not a dictionary let's try escaping it anyways. - # Worst case it will throw a Value error - ret = literal(ensure_bytes(args)) - - ensure_bytes = None # break circular reference - return ret - def _check_executed(self): if not self._executed: raise ProgrammingError("execute() first") @@ -279,8 +251,6 @@ def executemany(self, query, args): def _do_execute_many( self, prefix, values, postfix, args, max_stmt_length, encoding ): - conn = self._get_db() - escape = self._escape_args if isinstance(prefix, str): prefix = prefix.encode(encoding) if isinstance(values, str): @@ -289,11 +259,11 @@ def _do_execute_many( postfix = postfix.encode(encoding) sql = bytearray(prefix) args = iter(args) - v = values % escape(next(args), conn) + v = self._mogrify(values, next(args)) sql += v rows = 0 for arg in args: - v = values % escape(arg, conn) + v = self._mogrify(values, arg) if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length: rows += self.execute(sql + postfix) sql = bytearray(prefix) diff --git a/tests/default.cnf b/tests/default.cnf index 2aeda7cf..1d6c9421 100644 --- a/tests/default.cnf +++ b/tests/default.cnf @@ -2,9 +2,10 @@ # http://dev.mysql.com/doc/refman/5.1/en/option-files.html # and set TESTDB in your environment to the name of the file +# $ docker run -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -p 3306:3306 --rm --name mysqld mysql:latest [MySQLdb-tests] host = 127.0.0.1 -user = test +user = root database = test #password = -default-character-set = utf8 +default-character-set = utf8mb4 diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 5cb98910..1d2c3655 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -72,7 +72,7 @@ def test_executemany(): # values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9) # """ # list args - data = range(10) + data = [(i,) for i in range(10)] cursor.executemany("insert into test (data) values (%s)", data) assert cursor._executed.endswith( b",(7),(8),(9)" @@ -222,3 +222,24 @@ def test_cursor_discard_result(Cursor): "SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 31 AND 40" ) assert cursor.fetchone() == (31, "row 31") + + +def test_binary_prefix(): + # https://github.com/PyMySQL/mysqlclient/issues/494 + conn = connect(binary_prefix=True) + cursor = conn.cursor() + + cursor.execute("DROP TABLE IF EXISTS test_binary_prefix") + cursor.execute( + """\ +CREATE TABLE test_binary_prefix ( + id INTEGER NOT NULL AUTO_INCREMENT, + json JSON NOT NULL, + PRIMARY KEY (id) +) CHARSET=utf8mb4""" + ) + + cursor.executemany( + "INSERT INTO test_binary_prefix (id, json) VALUES (%(id)s, %(json)s)", + ({"id": 1, "json": "{}"}, {"id": 2, "json": "{}"}), + ) From 44d0f7a148474a4baefe4bb4a8836e192b6a88db Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 18:02:28 +0900 Subject: [PATCH 335/396] CI: Fix Django test (#606) --- .github/workflows/django.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml index b09d7823..737f34be 100644 --- a/.github/workflows/django.yaml +++ b/.github/workflows/django.yaml @@ -12,7 +12,7 @@ jobs: sudo systemctl start mysql.service mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" - mysql -uroot -proot -e "CREATE DATABASE django_test; CREATE DATABASE django_other;" + mysql -uroot -proot -e "CREATE DATABASE django_default; CREATE DATABASE django_other;" - uses: actions/checkout@v3 From b162dddcf318ddc0362a6c008d4ad165797ab9bb Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 20:08:04 +0900 Subject: [PATCH 336/396] Fix Connection.escape() with Unicode input (#608) After aed1dd2, Connection.escape() used ASCII to escape Unicode input. This commit makes it uses connection encoding instead. --- src/MySQLdb/_mysql.c | 64 +++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index b030af16..1f52d90b 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -943,7 +943,7 @@ _mysql_escape_string( { PyObject *str; char *in, *out; - int len; + unsigned long len; Py_ssize_t size; if (!PyArg_ParseTuple(args, "s#:escape_string", &in, &size)) return NULL; str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); @@ -980,10 +980,7 @@ _mysql_string_literal( _mysql_ConnectionObject *self, PyObject *o) { - PyObject *str, *s; - char *in, *out; - unsigned long len; - Py_ssize_t size; + PyObject *s; // input string or bytes. need to decref. if (self && PyModule_Check((PyObject*)self)) self = NULL; @@ -991,24 +988,44 @@ _mysql_string_literal( if (PyBytes_Check(o)) { s = o; Py_INCREF(s); - } else { - s = PyObject_Str(o); - if (!s) return NULL; - { - PyObject *t = PyUnicode_AsASCIIString(s); - Py_DECREF(s); - if (!t) return NULL; + } + else { + PyObject *t = PyObject_Str(o); + if (!t) return NULL; + + const char *encoding = (self && self->open) ? + _get_encoding(&self->connection) : utf8; + if (encoding == utf8) { s = t; } + else { + s = PyUnicode_AsEncodedString(t, encoding, "strict"); + Py_DECREF(t); + if (!s) return NULL; + } } - in = PyBytes_AsString(s); - size = PyBytes_GET_SIZE(s); - str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); + + // Prepare input string (in, size) + const char *in; + Py_ssize_t size; + if (PyUnicode_Check(s)) { + in = PyUnicode_AsUTF8AndSize(s, &size); + } else { + assert(PyBytes_Check(s)); + in = PyBytes_AsString(s); + size = PyBytes_GET_SIZE(s); + } + + // Prepare output buffer (str, out) + PyObject *str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); if (!str) { Py_DECREF(s); return PyErr_NoMemory(); } - out = PyBytes_AS_STRING(str); + char *out = PyBytes_AS_STRING(str); + + // escape + unsigned long len; if (self && self->open) { #if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) len = mysql_real_escape_string_quote(&(self->connection), out+1, in, size, '\''); @@ -1018,10 +1035,14 @@ _mysql_string_literal( } else { len = mysql_escape_string(out+1, in, size); } - *out = *(out+len+1) = '\''; - if (_PyBytes_Resize(&str, len+2) < 0) return NULL; + Py_DECREF(s); - return (str); + *out = *(out+len+1) = '\''; + if (_PyBytes_Resize(&str, len+2) < 0) { + Py_DECREF(str); + return NULL; + } + return str; } static PyObject * @@ -1499,8 +1520,9 @@ _mysql_ResultObject_discard( // do nothing } Py_END_ALLOW_THREADS - if (mysql_errno(self->conn)) { - return _mysql_Exception(self->conn); + _mysql_ConnectionObject *conn = (_mysql_ConnectionObject *)self->conn; + if (mysql_errno(&conn->connection)) { + return _mysql_Exception(conn); } Py_RETURN_NONE; } From ba859845051680031153735f0bb1fedbf7f0a302 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 20:21:11 +0900 Subject: [PATCH 337/396] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 23db1f27..d8ed79ca 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Install MySQL and mysqlclient: ``` # Assume you are activating Python 3 venv -$ brew install mysql +$ brew install mysql pkg-config $ pip install mysqlclient ``` @@ -58,9 +58,8 @@ If you don't want to install MySQL server, you can use mysql-client instead: ``` # Assume you are activating Python 3 venv -$ brew install mysql-client -$ echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.bash_profile -$ export PATH="/usr/local/opt/mysql-client/bin:$PATH" +$ brew install mysql-client pkg-config +$ export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql-client/lib/pkgconfig" $ pip install mysqlclient ``` From 398208f8c8322bc3604801ae26c4f7d742651b20 Mon Sep 17 00:00:00 2001 From: Matthias Schoettle Date: Thu, 18 May 2023 13:11:13 -0400 Subject: [PATCH 338/396] Fix mariadbclient SSL support (#609) --- src/MySQLdb/_mysql.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 1f52d90b..cc419776 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -531,10 +531,11 @@ _mysql_ConnectionObject_Initialize( // See https://github.com/PyMySQL/mysqlclient/issues/474 // TODO: Does MariaDB supports PREFERRED and VERIFY_CA? // We support only two levels for now. - if (sslmode_num >= SSLMODE_REQUIRED) { + my_bool enforce_tls = 1; + if (ssl_mode_num >= SSLMODE_REQUIRED) { mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&enforce_tls); } - if (sslmode_num >= SSLMODE_VERIFY_CA) { + if (ssl_mode_num >= SSLMODE_VERIFY_CA) { mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&enforce_tls); } #endif From 5dfab4d9c5c5bc141eb66ea761efe7a7b51e1956 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 May 2023 03:04:56 +0900 Subject: [PATCH 339/396] CI: Update Django test workflow (#610) --- .github/workflows/django.yaml | 32 ++++++++++++++++++++------------ Makefile | 4 ++-- ci/test_mysql.py | 6 ++++-- src/MySQLdb/cursors.py | 10 +++++++--- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml index 737f34be..a7e076ce 100644 --- a/.github/workflows/django.yaml +++ b/.github/workflows/django.yaml @@ -2,15 +2,22 @@ name: Django compat test on: push: + pull_request: jobs: build: + name: "Run Django LTS test suite" runs-on: ubuntu-latest + env: + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_DISABLE_PIP_VERSION_CHECK: 1 + DJANGO_VERSION: "3.2.19" steps: - name: Start MySQL run: | sudo systemctl start mysql.service mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql + mysql -uroot -proot -e "set global innodb_flush_log_at_trx_commit=0;" mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" mysql -uroot -proot -e "CREATE DATABASE django_default; CREATE DATABASE django_other;" @@ -19,27 +26,28 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - # https://www.mail-archive.com/django-updates@googlegroups.com/msg209056.html - python-version: "3.11" + # Django 3.2.9+ supports Python 3.10 + # https://docs.djangoproject.com/ja/3.2/releases/3.2/ + python-version: "3.10" + cache: "pip" + cache-dependency-path: "ci/django-requirements.txt" - name: Install mysqlclient - env: - PIP_NO_PYTHON_VERSION_WARNING: 1 - PIP_DISABLE_PIP_VERSION_CHECK: 1 run: | - pip install -r requirements.txt + #pip install -r requirements.txt + #pip install mysqlclient # Use stable version pip install . - # pip install mysqlclient # Use stable version - - name: Run Django test - env: - DJANGO_VERSION: "3.2.19" + - name: Setup Django run: | sudo apt-get install libmemcached-dev wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz tar xf ${DJANGO_VERSION}.tar.gz cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ + cd django-${DJANGO_VERSION} + pip install . -r tests/requirements/py3.txt + + - name: Run Django test + run: | cd django-${DJANGO_VERSION}/tests/ - pip install .. - pip install -r requirements/py3.txt PYTHONPATH=.. python3 ./runtests.py --settings=test_mysql diff --git a/Makefile b/Makefile index f0e94c3b..bcd4334d 100644 --- a/Makefile +++ b/Makefile @@ -17,5 +17,5 @@ clean: .PHONY: check check: - ruff . - black *.py src + ruff *.py src ci + black *.py src ci diff --git a/ci/test_mysql.py b/ci/test_mysql.py index e285f4cf..9417fc9f 100644 --- a/ci/test_mysql.py +++ b/ci/test_mysql.py @@ -19,7 +19,7 @@ "HOST": "127.0.0.1", "USER": "scott", "PASSWORD": "tiger", - "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, + "TEST": {"CHARSET": "utf8mb3", "COLLATION": "utf8mb3_general_ci"}, }, "other": { "ENGINE": "django.db.backends.mysql", @@ -27,7 +27,7 @@ "HOST": "127.0.0.1", "USER": "scott", "PASSWORD": "tiger", - "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, + "TEST": {"CHARSET": "utf8mb3", "COLLATION": "utf8mb3_general_ci"}, }, } @@ -37,3 +37,5 @@ PASSWORD_HASHERS = [ "django.contrib.auth.hashers.MD5PasswordHasher", ] + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index 785fa9a1..70fbeea4 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -66,7 +66,7 @@ def __init__(self, connection): self.connection = connection self.description = None self.description_flags = None - self.rowcount = -1 + self.rowcount = 0 self.arraysize = 1 self._executed = None @@ -78,8 +78,10 @@ def __init__(self, connection): def _discard(self): self.description = None self.description_flags = None - self.rowcount = -1 - self.lastrowid = None + # Django uses some member after __exit__. + # So we keep rowcount and lastrowid here. They are cleared in Cursor._query(). + # self.rowcount = 0 + # self.lastrowid = None self._rows = None self.rownumber = None @@ -323,6 +325,8 @@ def callproc(self, procname, args=()): def _query(self, q): db = self._get_db() self._result = None + self.rowcount = None + self.lastrowid = None db.query(q) self._do_get_result(db) self._post_get_result() From 640fe6de2a40f9e9600bad4e0be1465cae8f0f10 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 May 2023 13:20:33 +0900 Subject: [PATCH 340/396] Release v2.2.0rc1 (#607) --- HISTORY.rst | 13 +++++++++++-- src/MySQLdb/release.py | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 13e5cb01..ac083156 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,8 +5,17 @@ Release: TBD * Use ``pkg-config`` instead of ``mysql_config`` (#586) - - +* Raise ProgrammingError on -inf (#557) +* Raise IntegrityError for ER_BAD_NULL. (#579) +* Windows: Use MariaDB Connector/C 3.3.4 (#585) +* Use pkg-config instead of mysql_config (#586) +* Add collation option (#564) +* Drop Python 3.7 support (#593) +* Use pyproject.toml for build (#598) +* Add Cursor.mogrify (#477) +* Partial support of ssl_mode option with mariadbclient (#475) +* Discard remaining results without creating Python objects (#601) +* Fix executemany with binary prefix (#605) ====================== What's new in 2.1.1 diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 55359628..08b9e619 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -version_info = (2, 2, 0, "dev", 0) -__version__ = "2.2.0.dev0" +version_info = (2, 2, 0, "rc", 1) +__version__ = "2.2.0.rc1" From d48c8524cbf51378d868328e83af78855533c9cd Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 May 2023 13:44:56 +0900 Subject: [PATCH 341/396] CI: Fix Windows wheel build workflow --- .github/workflows/windows.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index bd711075..0c7184d2 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -2,9 +2,7 @@ name: Build windows wheels on: push: - branches: - - master - - ci + branches: ["main", "ci"] workflow_dispatch: jobs: From c650aa0d4ff3280045a2b776b012720094d35855 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 21 May 2023 02:03:59 +0900 Subject: [PATCH 342/396] CI: Use MariaDB (#611) --- .github/workflows/tests.yaml | 18 +++++++++++++++++- Makefile | 1 - ci/django-requirements.txt | 24 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 ci/django-requirements.txt diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 31ee34d3..743f1eb3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -2,6 +2,7 @@ name: Test on: push: + branches: ["main"] pull_request: jobs: @@ -10,10 +11,25 @@ jobs: strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] + include: + - python-version: "3.11" + mariadb: 1 steps: - - name: Start MySQL + - if: ${{ matrix.mariadb }} + name: Start MariaDB + # https://github.com/actions/runner-images/blob/9d9b3a110dfc98100cdd09cb2c957b9a768e2979/images/linux/scripts/installers/mysql.sh#L10-L13 + run: | + docker pull mariadb:10.11 + docker run -d -e MARIADB_ROOT_PASSWORD=root -p 3306:3306 --rm --name mariadb mariadb:10.11 + sudo apt-get -y install libmariadb-dev + mysql --version + mysql -uroot -proot -h127.0.0.1 -e "CREATE DATABASE mysqldb_test" + + - if: ${{ !matrix.mariadb }} + name: Start MySQL run: | sudo systemctl start mysql.service + mysql --version mysql -uroot -proot -e "CREATE DATABASE mysqldb_test" - uses: actions/checkout@v3 diff --git a/Makefile b/Makefile index bcd4334d..3f9ff8bb 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ doc: .PHONY: clean clean: - python setup.py clean find . -name '*.pyc' -delete find . -name '__pycache__' -delete rm -rf build diff --git a/ci/django-requirements.txt b/ci/django-requirements.txt new file mode 100644 index 00000000..83c8a8f2 --- /dev/null +++ b/ci/django-requirements.txt @@ -0,0 +1,24 @@ +# django-3.2.19/tests/requirements/py3.txt +aiosmtpd +asgiref >= 3.3.2 +argon2-cffi >= 16.1.0 +backports.zoneinfo; python_version < '3.9' +bcrypt +docutils +geoip2 +jinja2 >= 2.9.2 +numpy +Pillow >= 6.2.0 +# pylibmc/libmemcached can't be built on Windows. +pylibmc; sys.platform != 'win32' +pymemcache >= 3.4.0 +# RemovedInDjango41Warning. +python-memcached >= 1.59 +pytz +pywatchman; sys.platform != 'win32' +PyYAML +selenium +sqlparse >= 0.2.2 +tblib >= 1.5.0 +tzdata +colorama; sys.platform == 'win32' From d05a00eebc62fa22f5661fd7f61c2b250b0ec9b4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 21 May 2023 02:46:16 +0900 Subject: [PATCH 343/396] ci: Run django test after our test (#612) --- .github/workflows/codeql.yml | 1 - .github/workflows/django.yaml | 53 --------------------------- .github/workflows/lint.yaml | 5 ++- .github/workflows/tests.yaml | 67 ++++++++++++++++++++++++++++++---- .github/workflows/windows.yaml | 9 ++++- setup.py | 6 +-- 6 files changed, 73 insertions(+), 68 deletions(-) delete mode 100644 .github/workflows/django.yaml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ddc6d6e2..9bab6e0c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -4,7 +4,6 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] schedule: - cron: "29 15 * * 6" diff --git a/.github/workflows/django.yaml b/.github/workflows/django.yaml deleted file mode 100644 index a7e076ce..00000000 --- a/.github/workflows/django.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: Django compat test - -on: - push: - pull_request: - -jobs: - build: - name: "Run Django LTS test suite" - runs-on: ubuntu-latest - env: - PIP_NO_PYTHON_VERSION_WARNING: 1 - PIP_DISABLE_PIP_VERSION_CHECK: 1 - DJANGO_VERSION: "3.2.19" - steps: - - name: Start MySQL - run: | - sudo systemctl start mysql.service - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql - mysql -uroot -proot -e "set global innodb_flush_log_at_trx_commit=0;" - mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" - mysql -uroot -proot -e "CREATE DATABASE django_default; CREATE DATABASE django_other;" - - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - # Django 3.2.9+ supports Python 3.10 - # https://docs.djangoproject.com/ja/3.2/releases/3.2/ - python-version: "3.10" - cache: "pip" - cache-dependency-path: "ci/django-requirements.txt" - - - name: Install mysqlclient - run: | - #pip install -r requirements.txt - #pip install mysqlclient # Use stable version - pip install . - - - name: Setup Django - run: | - sudo apt-get install libmemcached-dev - wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz - tar xf ${DJANGO_VERSION}.tar.gz - cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ - cd django-${DJANGO_VERSION} - pip install . -r tests/requirements/py3.txt - - - name: Run Django test - run: | - cd django-${DJANGO_VERSION}/tests/ - PYTHONPATH=.. python3 ./runtests.py --settings=test_mysql diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 77a13c22..343b00fd 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,6 +1,9 @@ name: Lint -on: [push, pull_request] +on: + push: + branches: ["main"] + pull_request: jobs: lint: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 743f1eb3..c770ff9b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -6,8 +6,11 @@ on: pull_request: jobs: - build: + test: runs-on: ubuntu-latest + env: + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_DISABLE_PIP_VERSION_CHECK: 1 strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] @@ -38,15 +41,17 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "requirements.txt" - - name: Install dependencies - env: - PIP_NO_PYTHON_VERSION_WARNING: 1 - PIP_DISABLE_PIP_VERSION_CHECK: 1 + - name: Install mysqlclient run: | - pip install -r requirements.txt - pip install . + pip install -v . + - name: Install test dependencies + run: | + pip install -r requirements.txt + - name: Run tests env: TESTDB: actions.cnf @@ -54,3 +59,51 @@ jobs: pytest --cov=MySQLdb tests - uses: codecov/codecov-action@v3 + + django-test: + name: "Run Django LTS test suite" + needs: test + runs-on: ubuntu-latest + env: + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_DISABLE_PIP_VERSION_CHECK: 1 + DJANGO_VERSION: "3.2.19" + steps: + - name: Start MySQL + run: | + sudo systemctl start mysql.service + mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -proot mysql + mysql -uroot -proot -e "set global innodb_flush_log_at_trx_commit=0;" + mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" + mysql -uroot -proot -e "CREATE DATABASE django_default; CREATE DATABASE django_other;" + + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + # Django 3.2.9+ supports Python 3.10 + # https://docs.djangoproject.com/ja/3.2/releases/3.2/ + python-version: "3.10" + cache: "pip" + cache-dependency-path: "ci/django-requirements.txt" + + - name: Install mysqlclient + run: | + #pip install -r requirements.txt + #pip install mysqlclient # Use stable version + pip install . + + - name: Setup Django + run: | + sudo apt-get install libmemcached-dev + wget https://github.com/django/django/archive/${DJANGO_VERSION}.tar.gz + tar xf ${DJANGO_VERSION}.tar.gz + cp ci/test_mysql.py django-${DJANGO_VERSION}/tests/ + cd django-${DJANGO_VERSION} + pip install . -r tests/requirements/py3.txt + + - name: Run Django test + run: | + cd django-${DJANGO_VERSION}/tests/ + PYTHONPATH=.. python3 ./runtests.py --settings=test_mysql diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 0c7184d2..23c04320 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -3,6 +3,7 @@ name: Build windows wheels on: push: branches: ["main", "ci"] + pull_request: workflow_dispatch: jobs: @@ -67,8 +68,14 @@ jobs: CIBW_TEST_COMMAND: "python -c \"import MySQLdb; print(MySQLdb.version_info)\" " run: "python -m cibuildwheel --prerelease-pythons --output-dir dist" + - name: Build sdist + working-directory: mysqlclient + run: | + python -m pip install build + python -m build -s -o dist + - name: Upload Wheel uses: actions/upload-artifact@v3 with: name: win-wheels - path: mysqlclient/dist/*.whl + path: mysqlclient/dist/*.* diff --git a/setup.py b/setup.py index 5594df54..2fa4cbbd 100644 --- a/setup.py +++ b/setup.py @@ -78,10 +78,6 @@ def get_config_posix(options=None): if static: ext_options["language"] = "c++" - print("Options for building extention module:") - for k, v in ext_options.items(): - print(f" {k}: {v}") - return ext_options @@ -157,7 +153,7 @@ def get_options(): else: ext_options = get_config_posix(get_options()) -print("# Extention options") +print("# Options for building extention module:") for k, v in ext_options.items(): print(f" {k}: {v}") From ae1a098930994a291158cbf8858a65578e40d65b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 03:23:57 +0900 Subject: [PATCH 344/396] Configure Renovate (#616) --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..39a2b6e9 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} From 1e1405587caf250b2fddff8ae5e45e17e5adccda Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 22 Jun 2023 14:51:11 +0900 Subject: [PATCH 345/396] Release v2.2.0 (#618) --- HISTORY.rst | 2 +- src/MySQLdb/release.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index ac083156..377ce83b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,7 +2,7 @@ What's new in 2.2.0 ====================== -Release: TBD +Release: 2023-06-22 * Use ``pkg-config`` instead of ``mysql_config`` (#586) * Raise ProgrammingError on -inf (#557) diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 08b9e619..273a297d 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -version_info = (2, 2, 0, "rc", 1) -__version__ = "2.2.0.rc1" +__version__ = "2.2.0" +version_info = (2, 2, 0, "final", 0) From eb5cb3d564e8eec632b51147dfb8c929fbd05fd1 Mon Sep 17 00:00:00 2001 From: Mario Haustein Date: Thu, 22 Jun 2023 15:10:48 +0200 Subject: [PATCH 346/396] Add build dependency for `pkg-config` to README (#621) Fix #620 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8ed79ca..c7dc4855 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,8 @@ support in some user forum. Don't file a issue on the issue tracker.** You may need to install the Python 3 and MySQL development headers and libraries like so: -* `$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essential` # Debian / Ubuntu -* `% sudo yum install python3-devel mysql-devel` # Red Hat / CentOS +* `$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essential pkg-config` # Debian / Ubuntu +* `% sudo yum install python3-devel mysql-devel pkgconfig` # Red Hat / CentOS Then you can install mysqlclient via pip now: From df302ff62f154a85221fa9f424765fbac35026aa Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 23 Jun 2023 04:04:59 +0900 Subject: [PATCH 347/396] Add discussion template --- .github/DISCUSSION_TEMPLATE/build.yml | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/DISCUSSION_TEMPLATE/build.yml diff --git a/.github/DISCUSSION_TEMPLATE/build.yml b/.github/DISCUSSION_TEMPLATE/build.yml new file mode 100644 index 00000000..c2e93df4 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/build.yml @@ -0,0 +1,49 @@ +title: "[Build] " +body: + - type: input + id: os + attributes: + label: What OS and which version do you use? + description: | + e.g. + - Windows 11 + - macOS 13.4 + - Ubuntu 22.04 + + - type: textarea + id: libmysqlclient + attributes: + label: How did you installed mysql client library? + description: | + e.g. + - `apt-get install libmysqlclient-dev` + - `brew install mysql-client` + - `brew install mysql` + render: bash + + - type: textarea + id: pkgconfig-output + attributes: + label: Output from `pkg-config --cflags --libs mysqlclient` + description: If you are using mariadbclient, run `pkg-config --cflags --libs mariadb` instead. + render: bash + + - type: input + id: mysqlclient-install + attributes: + label: How did you tried to install mysqlclient? + description: | + e.g. + - `pip install mysqlclient` + - `poetry add mysqlclient` + + - type: textarea + id: mysqlclient-error + attributes: + label: Output of building mysqlclient + description: not only error message. full log from start installing mysqlclient. + render: bash + + + + From 020f040ce169d05331c64fca64198f752fae8f69 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 23 Jun 2023 04:09:24 +0900 Subject: [PATCH 348/396] Add issue template config --- .github/ISSUE_TEMPLATE/config.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..6116b043 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Failed to build + url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=build" + about: Ask help in this forum. From 1b29c60d449d1822936dc535c19be7f3c1d46848 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 23 Jun 2023 05:05:24 +0900 Subject: [PATCH 349/396] Update bugreport issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 58 ----------------- .github/ISSUE_TEMPLATE/bugreport.yml | 93 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 58 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bugreport.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 50d2bdc2..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Bug report -about: Report an issue of this project -title: '' -labels: '' -assignees: '' - ---- - -### Read this first - -We don't use this issue tracker to help users. If you had trouble, please ask it on some user community. See [here](https://github.com/PyMySQL/mysqlclient-python#support). -Please use this tracker only when you are sure about it is an issue of this software. - -And please provide full information from first. I don't want to ask questions like "What is your Python version?", "Do you confirm MySQL error log?". If the issue report looks incomplete, I will just close it. - -Are you ready? Please remove until here and make a good issue report!! - - -### Describe the bug - -A clear and concise description of what the bug is. - -### To Reproduce - -**Schema** - -``` -create table .... -``` - -**Code** - -```py -con = MySQLdb.connect(...) -cur = con.cursor() -cur.execute(...) -``` - -### Environment - -**MySQL Server** - -- Server OS (e.g. Windows 10, Ubuntu 20.04): -- Server Version (e.g. MariaDB 10.3.16): - -**MySQL Client** - -- OS (e.g. Windows 10, Ubuntu 20.04): - -- Python (e.g. Homebrew Python 3.7.5): - -- Connector/C (e.g. Homebrew mysql-client 8.0.18): - - -### Additional context - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bugreport.yml b/.github/ISSUE_TEMPLATE/bugreport.yml new file mode 100644 index 00000000..a3dc04a3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bugreport.yml @@ -0,0 +1,93 @@ +name: Bug Report +description: File a bug report +body: + - type: markdown + attributes: + value: | + We don't use this issue tracker to help users. + Please use this tracker only when you are sure about it is an issue of this software. + + If you had trouble, please ask it on some user community. + + - [Python Discord](https://www.pythondiscord.com/) + For general Python questions, including developing application using MySQL. + + - [MySQL Community Slack](https://lefred.be/mysql-community-on-slack/) + For general MySQL questions. + + - [mysqlclient Discuss](https://github.com/PyMySQL/mysqlclient/discussions) + For mysqlclient specific topics. + + - type: textarea + id: describe + attributes: + label: Describe the bug + description: "A **clear and concise** description of what the bug is." + + - type: textarea + id: environments + attributes: + label: Environment + description: | + - Server and version (e.g. MySQL 8.0.33, MariaDB 10.11.4) + - OS (e.g. Windows 11, Ubuntu 22.04, macOS 13.4.1) + - Python version + + - type: input + id: libmysqlclient + attributes: + label: How did you install libmysqlclient libraries? + description: | + e.g. brew install mysql-cleint, brew install mariadb, apt-get install libmysqlclient-dev + + - type: input + id: mysqlclient-version + attributes: + label: What version of mysqlclient do you use? + + - type: markdown + attributes: + value: | + ## Complete step to reproduce. + # + Do not expect maintainer complement any piece of code, schema, and data need to reproduce. + You need to provide **COMPLETE** step to reproduce. + + It is very recommended to use Docker to start MySQL server. + Maintainer can not use your Database to reproduce your issue. + + **If you write only little code snippet, maintainer may close your issue + without any comment.** + + - type: textarea + id: reproduce-docker + attributes: + label: Docker command to start MySQL server + render: bash + description: e.g. `docker run -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -p 3306:3306 --rm --name mysql mysql:8.0` + + - type: textarea + id: reproduce-code + attributes: + label: Minimum but complete code to reproduce + render: python + value: | + # Write Python code here. + import MySQLdb + + conn = MySQLdb.connect(host='127.0.0.1', port=3306, user='root') + ... + + - type: textarea + id: reproduce-schema + attributes: + label: Schema and initial data required to reproduce. + render: sql + value: | + -- Write SQL here. + -- e.g. CREATE TABLE ... + + - type: textarea + id: reproduce-other + attributes: + label: Commands, and any other step required to reproduce your issue. From 4f2a8393c79a1ef18db8bb874f205145fa272f73 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 23 Jun 2023 05:07:28 +0900 Subject: [PATCH 350/396] update issue config --- .github/ISSUE_TEMPLATE/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 6116b043..198b7e02 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,8 @@ contact_links: - name: Failed to build + about: Ask help for build error. url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=build" - about: Ask help in this forum. + + - name: Ask question + about: Ask other questions. + url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=q-a" From 807933eb2547f26b9adb5c72960d99c791d8021b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 29 Jun 2023 17:36:30 +0900 Subject: [PATCH 351/396] Support pkg-config libmariadb (#631) Fix #629. --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2fa4cbbd..f7f3d924 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,8 @@ def find_package_name(): """Get available pkg-config package name""" - packages = ["mysqlclient", "mariadb"] + # Ubuntu uses mariadb.pc, but CentOS uses libmariadb.pc + packages = ["mysqlclient", "mariadb", "libmariadb"] for pkg in packages: try: cmd = f"pkg-config --exists {pkg}" @@ -153,7 +154,7 @@ def get_options(): else: ext_options = get_config_posix(get_options()) -print("# Options for building extention module:") +print("# Options for building extension module:") for k, v in ext_options.items(): print(f" {k}: {v}") From 1468dce0737d1d042fd5431bb048ff13a51cc936 Mon Sep 17 00:00:00 2001 From: raceybe Date: Thu, 29 Jun 2023 23:22:35 -0400 Subject: [PATCH 352/396] Update README.md (#633) fixed typo in Customize build (POSIX) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7dc4855..c6c8a5a8 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ $ pip install mysqlclient ### Customize build (POSIX) -mysqlclient uses `pkg-config --clfags --ldflags mysqlclient` by default for finding +mysqlclient uses `pkg-config --cflags --ldflags mysqlclient` by default for finding compiler/linker flags. You can use `MYSQLCLIENT_CFLAGS` and `MYSQLCLIENT_LDFLAGS` environment From 8878212ee94e905b33333988f411faeb96e6f538 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 2 Jul 2023 03:04:28 +0900 Subject: [PATCH 353/396] Move issue report to discussions --- .../bugreport.yml => DISCUSSION_TEMPLATE/issue-report.yml} | 4 ++-- .github/ISSUE_TEMPLATE/config.yml | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) rename .github/{ISSUE_TEMPLATE/bugreport.yml => DISCUSSION_TEMPLATE/issue-report.yml} (96%) diff --git a/.github/ISSUE_TEMPLATE/bugreport.yml b/.github/DISCUSSION_TEMPLATE/issue-report.yml similarity index 96% rename from .github/ISSUE_TEMPLATE/bugreport.yml rename to .github/DISCUSSION_TEMPLATE/issue-report.yml index a3dc04a3..1e4d293c 100644 --- a/.github/ISSUE_TEMPLATE/bugreport.yml +++ b/.github/DISCUSSION_TEMPLATE/issue-report.yml @@ -1,9 +1,9 @@ -name: Bug Report -description: File a bug report body: - type: markdown attributes: value: | + Failed to buid? [Use this form](https://github.com/PyMySQL/mysqlclient/discussions/new?category=build). + We don't use this issue tracker to help users. Please use this tracker only when you are sure about it is an issue of this software. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 198b7e02..dc88cb1a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,10 @@ contact_links: about: Ask help for build error. url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=build" + - name: Report issue + about: Found bug? + url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=issue-report" + - name: Ask question about: Ask other questions. url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=q-a" From fc6b9da632fa6cd7170e77421ecf86653817327f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 2 Jul 2023 03:07:09 +0900 Subject: [PATCH 354/396] fix templates --- .github/DISCUSSION_TEMPLATE/{build.yml => build-issue.yml} | 0 .github/DISCUSSION_TEMPLATE/issue-report.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename .github/DISCUSSION_TEMPLATE/{build.yml => build-issue.yml} (100%) diff --git a/.github/DISCUSSION_TEMPLATE/build.yml b/.github/DISCUSSION_TEMPLATE/build-issue.yml similarity index 100% rename from .github/DISCUSSION_TEMPLATE/build.yml rename to .github/DISCUSSION_TEMPLATE/build-issue.yml diff --git a/.github/DISCUSSION_TEMPLATE/issue-report.yml b/.github/DISCUSSION_TEMPLATE/issue-report.yml index 1e4d293c..6724fcf8 100644 --- a/.github/DISCUSSION_TEMPLATE/issue-report.yml +++ b/.github/DISCUSSION_TEMPLATE/issue-report.yml @@ -2,7 +2,7 @@ body: - type: markdown attributes: value: | - Failed to buid? [Use this form](https://github.com/PyMySQL/mysqlclient/discussions/new?category=build). + Failed to buid? [Use this form](https://github.com/PyMySQL/mysqlclient/discussions/new?category=build-issue). We don't use this issue tracker to help users. Please use this tracker only when you are sure about it is an issue of this software. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index dc88cb1a..9f1273ed 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ contact_links: - name: Failed to build about: Ask help for build error. - url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=build" + url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=build-issue" - name: Report issue about: Found bug? From a72b7aa707fb457ef80b613ba08e0872b51e6f30 Mon Sep 17 00:00:00 2001 From: Stevie Gayet <87695919+stegayet@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:54:30 +0200 Subject: [PATCH 355/396] ci: add Python 3.12 in test matrix (#644) --- .github/workflows/tests.yaml | 3 ++- pyproject.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c770ff9b..8380e09e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,7 +13,7 @@ jobs: PIP_DISABLE_PIP_VERSION_CHECK: 1 strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] include: - python-version: "3.11" mariadb: 1 @@ -43,6 +43,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: "pip" cache-dependency-path: "requirements.txt" + allow-prereleases: true - name: Install mysqlclient run: | diff --git a/pyproject.toml b/pyproject.toml index 3bfd1f6c..0ad7ae58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Database", "Topic :: Database :: Database Engines/Servers", ] From 91c04282eac46ce2e5380be58e262d817faab566 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 03:15:46 +0900 Subject: [PATCH 356/396] Update actions/checkout action to v4 (#654) --- .github/workflows/codeql.yml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/tests.yaml | 4 ++-- .github/workflows/windows.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9bab6e0c..b496b5a9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 343b00fd..8e0e2947 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -9,6 +9,6 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: psf/black@stable - uses: chartboost/ruff-action@v1 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8380e09e..2ca99a3f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -35,7 +35,7 @@ jobs: mysql --version mysql -uroot -proot -e "CREATE DATABASE mysqldb_test" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 @@ -78,7 +78,7 @@ jobs: mysql -uroot -proot -e "CREATE USER 'scott'@'%' IDENTIFIED BY 'tiger'; GRANT ALL ON *.* TO scott;" mysql -uroot -proot -e "CREATE DATABASE django_default; CREATE DATABASE django_other;" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 23c04320..661b7793 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -40,7 +40,7 @@ jobs: cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake - name: Checkout mysqlclient - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: mysqlclient From 0d0fff7662abf6e46324d26415ef3f4cb70f77db Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 13 Nov 2023 17:36:09 +0900 Subject: [PATCH 357/396] Do not use MYSQL_OPT_RECONNECT as possible. (#664) MySQL 8.0.33+ shows deprecation warning to stderr. So we avoid using it as possible. In the future, we will deprecate `reconnect` option of the `Connection.ping()`. --- src/MySQLdb/_mysql.c | 40 ++++++++++++++++++++++++-------------- src/MySQLdb/connections.py | 4 +++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index cc419776..0612be26 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -25,13 +25,14 @@ USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - +#include #include "mysql.h" #include "mysqld_error.h" #if MYSQL_VERSION_ID >= 80000 // https://github.com/mysql/mysql-server/commit/eb821c023cedc029ca0b06456dfae365106bee84 -#define my_bool _Bool +// my_bool was typedef of char before MySQL 8.0.0. +#define my_bool bool #endif #if ((MYSQL_VERSION_ID >= 50555 && MYSQL_VERSION_ID <= 50599) || \ @@ -71,7 +72,8 @@ static PyObject *_mysql_NotSupportedError; typedef struct { PyObject_HEAD MYSQL connection; - int open; + bool open; + bool reconnect; PyObject *converter; } _mysql_ConnectionObject; @@ -443,7 +445,8 @@ _mysql_ConnectionObject_Initialize( *auth_plugin=NULL; self->converter = NULL; - self->open = 0; + self->open = false; + self->reconnect = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ssssisOiiisssiOsiiiss:connect", @@ -487,7 +490,7 @@ _mysql_ConnectionObject_Initialize( PyErr_SetNone(PyExc_MemoryError); return -1; } - self->open = 1; + self->open = true; if (connect_timeout) { unsigned int timeout = connect_timeout; @@ -686,7 +689,7 @@ _mysql_ConnectionObject_close( Py_BEGIN_ALLOW_THREADS mysql_close(&(self->connection)); Py_END_ALLOW_THREADS - self->open = 0; + self->open = false; _mysql_ConnectionObject_clear(self); Py_RETURN_NONE; } @@ -1852,18 +1855,18 @@ _mysql_ResultObject_num_rows( } static char _mysql_ConnectionObject_ping__doc__[] = -"Checks whether or not the connection to the server is\n\ -working. If it has gone down, an automatic reconnection is\n\ -attempted.\n\ +"Checks whether or not the connection to the server is working.\n\ \n\ This function can be used by clients that remain idle for a\n\ long while, to check whether or not the server has closed the\n\ -connection and reconnect if necessary.\n\ +connection.\n\ \n\ New in 1.2.2: Accepts an optional reconnect parameter. If True,\n\ then the client will attempt reconnection. Note that this setting\n\ is persistent. By default, this is on in MySQL<5.0.3, and off\n\ thereafter.\n\ +MySQL 8.0.33 deprecated the MYSQL_OPT_RECONNECT option so reconnect\n\ +parameter is also deprecated in mysqlclient 2.2.1.\n\ \n\ Non-standard. You should assume that ping() performs an\n\ implicit rollback; use only when starting a new transaction.\n\ @@ -1875,17 +1878,24 @@ _mysql_ConnectionObject_ping( _mysql_ConnectionObject *self, PyObject *args) { - int r, reconnect = -1; - if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; + int reconnect = 0; + if (!PyArg_ParseTuple(args, "|p", &reconnect)) return NULL; check_connection(self); - if (reconnect != -1) { + if (reconnect != (self->reconnect == true)) { + // libmysqlclient show warning to stderr when MYSQL_OPT_RECONNECT is used. + // so we avoid using it as possible for now. + // TODO: Warn when reconnect is true. + // MySQL 8.0.33 show warning to stderr already. + // We will emit Pytohn warning in future. my_bool recon = (my_bool)reconnect; mysql_options(&self->connection, MYSQL_OPT_RECONNECT, &recon); + self->reconnect = (bool)reconnect; } + int r; Py_BEGIN_ALLOW_THREADS r = mysql_ping(&(self->connection)); Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); + if (r) return _mysql_Exception(self); Py_RETURN_NONE; } @@ -2165,7 +2175,7 @@ _mysql_ConnectionObject_dealloc( PyObject_GC_UnTrack(self); if (self->open) { mysql_close(&(self->connection)); - self->open = 0; + self->open = false; } Py_CLEAR(self->converter); MyFree(self); diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py index 865d129a..7456aeac 100644 --- a/src/MySQLdb/connections.py +++ b/src/MySQLdb/connections.py @@ -192,7 +192,9 @@ class object, used to create cursors (keyword only) super().__init__(*args, **kwargs2) self.cursorclass = cursorclass - self.encoders = {k: v for k, v in conv.items() if type(k) is not int} + self.encoders = { + k: v for k, v in conv.items() if type(k) is not int # noqa: E721 + } self._server_version = tuple( [numeric_part(n) for n in self.get_server_info().split(".")[:2]] From cb236e9fefe211594a5f8e71eed26f0d36791105 Mon Sep 17 00:00:00 2001 From: Benjamin Loison <12752145+Benjamin-Loison@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:53:15 +0100 Subject: [PATCH 358/396] add bash syntax highlighting to `README.md` (#665) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c6c8a5a8..9c65ed7e 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,16 @@ $ pip install mysqlclient Install MySQL and mysqlclient: -``` -# Assume you are activating Python 3 venv +```bash +$ # Assume you are activating Python 3 venv $ brew install mysql pkg-config $ pip install mysqlclient ``` If you don't want to install MySQL server, you can use mysql-client instead: -``` -# Assume you are activating Python 3 venv +```bash +$ # Assume you are activating Python 3 venv $ brew install mysql-client pkg-config $ export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql-client/lib/pkgconfig" $ pip install mysqlclient @@ -88,7 +88,7 @@ compiler/linker flags. You can use `MYSQLCLIENT_CFLAGS` and `MYSQLCLIENT_LDFLAGS` environment variables to customize compiler/linker options. -``` +```bash $ export MYSQLCLIENT_CFLAGS=`pkg-config mysqlclient --cflags` $ export MYSQLCLIENT_LDFLAGS=`pkg-config mysqlclient --libs` $ pip install mysqlclient From 2e86ccec70b7863922d192afc3167718e4fb8714 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 15 Nov 2023 19:14:04 +0900 Subject: [PATCH 359/396] use sphinx-rtd-theme (#668) --- .readthedocs.yaml | 6 ++++++ doc/conf.py | 6 +++--- doc/requirements.txt | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 doc/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index eaffaf39..0b288620 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,6 +4,12 @@ build: os: ubuntu-22.04 tools: python: "3.11" + apt_packages: - default-libmysqlclient-dev - build-essential + +python: + install: + - requirements: doc/requirements.txt + diff --git a/doc/conf.py b/doc/conf.py index 3e919822..e06003ff 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,8 +49,8 @@ master_doc = "index" # General information about the project. -project = "MySQLdb" -copyright = "2012, Andy Dustman" +project = "mysqlclient" +copyright = "2023, Inada Naoki" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -100,7 +100,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "default" +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..8d45d0b6 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +sphinx~=7.2 +sphinx-rtd-theme~=1.3.0 From 3805b28abc24cc985ffe7c8a05dd4bd6530aefe8 Mon Sep 17 00:00:00 2001 From: Alex Schmitz Date: Wed, 15 Nov 2023 04:51:10 -0600 Subject: [PATCH 360/396] docs: Improve portability of brew prefix (#667) homebrew's prefix can vary - with this minor change, the pkgconfig path should work in more user environments. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c65ed7e..451ce799 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ If you don't want to install MySQL server, you can use mysql-client instead: ```bash $ # Assume you are activating Python 3 venv $ brew install mysql-client pkg-config -$ export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql-client/lib/pkgconfig" +$ export PKG_CONFIG_PATH="$(brew --prefix)/opt/mysql-client/lib/pkgconfig" $ pip install mysqlclient ``` From f82dac326f2344e306fa3885d425cabe33f502b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:55:13 +0900 Subject: [PATCH 361/396] chore(deps): update dependency sphinx-rtd-theme to v2 (#671) --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 8d45d0b6..01406623 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,2 +1,2 @@ sphinx~=7.2 -sphinx-rtd-theme~=1.3.0 +sphinx-rtd-theme~=2.0.0 From 16017b73546522bc83787e8daf2fd43f37385bc4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:02:29 +0900 Subject: [PATCH 362/396] chore(deps): update actions/setup-python action to v5 (#674) --- .github/workflows/tests.yaml | 4 ++-- .github/workflows/windows.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2ca99a3f..f6a13fd2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "pip" @@ -81,7 +81,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: # Django 3.2.9+ supports Python 3.10 # https://docs.djangoproject.com/ja/3.2/releases/3.2/ diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 661b7793..59ef8f21 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -57,7 +57,7 @@ jobs: EOF cat site.cfg - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.12.3 - name: Build wheels From 6831bc2f34271e518758b2bce6414e6df2809461 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 8 Dec 2023 21:36:57 +0900 Subject: [PATCH 363/396] win: update mariadb connector/c to 3.3.8 (#676) --- .github/workflows/windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 59ef8f21..afbfd4c8 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -10,7 +10,7 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.3.4" + CONNECTOR_VERSION: "3.3.8" steps: - name: Cache Connector @@ -59,7 +59,7 @@ jobs: - uses: actions/setup-python@v5 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.12.3 + run: python -m pip install cibuildwheel - name: Build wheels working-directory: mysqlclient env: From d82d0fd1e7e22bd03bdf417122302307d28ce946 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 13 Dec 2023 22:23:33 +0900 Subject: [PATCH 364/396] Release v2.2.1 (#679) --- HISTORY.rst | 13 +++++++++++++ src/MySQLdb/release.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 377ce83b..99ff66cd 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,16 @@ +====================== + What's new in 2.2.1 +====================== + +Release: 2023-12-13 + +* ``Connection.ping()`` avoid using ``MYSQL_OPT_RECONNECT`` option until + ``reconnect=True`` is specified. MySQL 8.0.33 start showing warning + when the option is used. (#664) +* Windows: Update MariaDB Connector/C to 3.3.8. (#665) +* Windows: Build wheels for Python 3.12 (#644) + + ====================== What's new in 2.2.0 ====================== diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 273a297d..c5281a5a 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.0" -version_info = (2, 2, 0, "final", 0) +__version__ = "2.2.1" +version_info = (2, 2, 1, "final", 0) From f67ad12d24783ff04fee40f920aec2213b9b0c14 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:05:34 +0900 Subject: [PATCH 365/396] chore(deps): update github/codeql-action action to v3 (#680) --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b496b5a9..075081b8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # with: # languages: ${{ matrix.language }} # queries: +security-and-quality @@ -34,4 +34,4 @@ jobs: python setup.py build_ext -if - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From a2212b33821f4a34f342a335fc7af0c16d18a8d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 14:12:50 +0900 Subject: [PATCH 366/396] chore(deps): update actions/upload-artifact action to v4 (#681) --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index afbfd4c8..01dfc283 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -75,7 +75,7 @@ jobs: python -m build -s -o dist - name: Upload Wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: win-wheels path: mysqlclient/dist/*.* From e2a908bc28c5fb877ef4f7fefe9df85c94fba88f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:57:52 +0900 Subject: [PATCH 367/396] update actions/cache action to v4 (#684) --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 01dfc283..ec79ca4c 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -15,7 +15,7 @@ jobs: - name: Cache Connector id: cache-connector - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: c:/mariadb-connector key: mariadb-connector-c-${{ env.CONNECTOR_VERSION }}-win From 9e3b00fe1962b54f7082275ab79953d2745b9d9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:57:09 +0900 Subject: [PATCH 368/396] chore(deps): update codecov/codecov-action action to v4 (#689) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f6a13fd2..5545b885 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -59,7 +59,7 @@ jobs: run: | pytest --cov=MySQLdb tests - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 django-test: name: "Run Django LTS test suite" From 720b80497f0570333d67bc021692c1ec937b8d97 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 3 Feb 2024 19:16:26 +0900 Subject: [PATCH 369/396] Support MySQL 8.3 (#690) Fix #688 --- src/MySQLdb/_mysql.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 0612be26..6d09e416 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -524,8 +524,13 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); if (ssl) { - mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); + mysql_options(&(self->connection), MYSQL_OPT_SSL_KEY, key); + mysql_options(&(self->connection), MYSQL_OPT_SSL_CERT, cert); + mysql_options(&(self->connection), MYSQL_OPT_SSL_CA, ca); + mysql_options(&(self->connection), MYSQL_OPT_SSL_CAPATH, capath); + mysql_options(&(self->connection), MYSQL_OPT_SSL_CIPHER, cipher); } + if (ssl_mode) { #ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE mysql_options(&(self->connection), MYSQL_OPT_SSL_MODE, &ssl_mode_num); @@ -1789,10 +1794,11 @@ _mysql_ConnectionObject_kill( { unsigned long pid; int r; + char query[50]; if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; check_connection(self); Py_BEGIN_ALLOW_THREADS - r = mysql_kill(&(self->connection), pid); + r = mysql_query(&(self->connection), snprintf(query, 50, "KILL %d", pid)); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); Py_RETURN_NONE; @@ -2008,7 +2014,7 @@ _mysql_ConnectionObject_shutdown( int r; check_connection(self); Py_BEGIN_ALLOW_THREADS - r = mysql_shutdown(&(self->connection), SHUTDOWN_DEFAULT); + r = mysql_query(&(self->connection), "SHUTDOWN"); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); Py_RETURN_NONE; From 2570ad48b8023ad79a5495c253abab494c30463e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 4 Feb 2024 12:31:52 +0900 Subject: [PATCH 370/396] use ruff for formatter (#691) --- .github/workflows/codeql.yml | 37 ------------------------------------ .github/workflows/lint.yaml | 5 +++-- src/MySQLdb/connections.py | 4 +++- 3 files changed, 6 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 075081b8..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [ "main" ] - pull_request: - schedule: - - cron: "29 15 * * 6" - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - # with: - # languages: ${{ matrix.language }} - # queries: +security-and-quality - - # - name: Autobuild - # uses: github/codeql-action/autobuild@v2 - - - name: Build - run: | - python setup.py build_ext -if - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 8e0e2947..95a95ac3 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,5 +10,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: psf/black@stable - - uses: chartboost/ruff-action@v1 + - run: pipx install ruff + - run: ruff check src/ + - run: ruff format src/ diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py index 7456aeac..4fa762d4 100644 --- a/src/MySQLdb/connections.py +++ b/src/MySQLdb/connections.py @@ -193,7 +193,9 @@ class object, used to create cursors (keyword only) super().__init__(*args, **kwargs2) self.cursorclass = cursorclass self.encoders = { - k: v for k, v in conv.items() if type(k) is not int # noqa: E721 + k: v + for k, v in conv.items() + if type(k) is not int # noqa: E721 } self._server_version = tuple( From 8b032d134e4a0fe05b1d0a08b6fd5c7275c18011 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 4 Feb 2024 13:40:02 +0900 Subject: [PATCH 371/396] doc: add link to README (#692) Fix #650 --- doc/user_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 5c9577bc..8b057e08 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -14,7 +14,7 @@ database server that provides the Python database API. Installation ------------ -The ``README`` file has complete installation instructions. +The `README `_ file has complete installation instructions. MySQLdb._mysql From ace8b256493f848ae57c5fa8a82504c5d7b24657 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 4 Feb 2024 13:41:50 +0900 Subject: [PATCH 372/396] deprecate APIs deprecated in MySQL 8 --- src/MySQLdb/_mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 6d09e416..8ea32e08 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -1785,7 +1785,7 @@ _mysql_ConnectionObject_insert_id( static char _mysql_ConnectionObject_kill__doc__[] = "Asks the server to kill the thread specified by pid.\n\ -Non-standard."; +Non-standard. Deprecated."; static PyObject * _mysql_ConnectionObject_kill( @@ -2003,7 +2003,7 @@ _mysql_ConnectionObject_select_db( static char _mysql_ConnectionObject_shutdown__doc__[] = "Asks the database server to shut down. The connected user must\n\ -have shutdown privileges. Non-standard.\n\ +have shutdown privileges. Non-standard. Deprecated.\n\ "; static PyObject * From 7c199a90f8554727a66c035058f13211f6774836 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 4 Feb 2024 14:22:58 +0900 Subject: [PATCH 373/396] release 2.2.2 (#693) --- HISTORY.rst | 12 ++++++++++++ src/MySQLdb/release.py | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 99ff66cd..87bb2790 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,15 @@ +====================== + What's new in 2.2.2 +====================== + +Release: 2024-02-04 + +* Support building with MySQL 8.3 (#688). +* Deprecate ``db.shutdown()`` and ``db.kill()`` methods in docstring. + This is because ``mysql_shutdown()`` and ``mysql_kill()`` were removed in MySQL 8.3. + They will emit DeprecationWarning in the future but not for now. + + ====================== What's new in 2.2.1 ====================== diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index c5281a5a..614020dc 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.1" -version_info = (2, 2, 1, "final", 0) +__version__ = "2.2.2" +version_info = (2, 2, 2, "final", 0) From b34f8ef174e55eb51e3f5e43aeb78cea36e9e34a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 4 Feb 2024 21:43:11 +0900 Subject: [PATCH 374/396] fix build issue introduced in 2.2.2 (#696) --- src/MySQLdb/_mysql.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 8ea32e08..c334db66 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -1797,8 +1797,9 @@ _mysql_ConnectionObject_kill( char query[50]; if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; check_connection(self); + snprintf(query, 50, "KILL %lu", pid); Py_BEGIN_ALLOW_THREADS - r = mysql_query(&(self->connection), snprintf(query, 50, "KILL %d", pid)); + r = mysql_query(&(self->connection), query); Py_END_ALLOW_THREADS if (r) return _mysql_Exception(self); Py_RETURN_NONE; From 1fa31fd17d20cb582fc58469a86b071cb84cee7f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 4 Feb 2024 21:49:02 +0900 Subject: [PATCH 375/396] release 2.2.3 (#697) --- HISTORY.rst | 9 +++++++++ src/MySQLdb/release.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 87bb2790..b57251c7 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,12 @@ +====================== + What's new in 2.2.3 +====================== + +Release: 2024-02-04 + +* Fix ``Connection.kill()`` method that broken in 2.2.2. (#689) + + ====================== What's new in 2.2.2 ====================== diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 614020dc..6bc1089f 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.2" -version_info = (2, 2, 2, "final", 0) +__version__ = "2.2.3" +version_info = (2, 2, 3, "final", 0) From a7e3887c8ac8d1b2a34b13f19da3cc164c4aa74c Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 9 Feb 2024 00:21:36 +0900 Subject: [PATCH 376/396] Support `ssl=True` (#700) MySQL use ssl by default but MariaDB don't. Until mysqlclient<=2.2.1, `ssl=True` unintentionally allowed and it called `mysql_ssl_set(mysql, NULL, NULL, NULL, NULL, NULL)`. Although it is no-op in MySQL Connector, MariaDB Connector silently set MYSQL_OPT_SSL_ENFORCE when the API is called. (See #698) In case of PyMySQL, ssl is not used by default but `ssl=True` behave like `sslmode="PREFERRED"`. For better backward compatibility and compatibility with PyMySQL and security, I decided to allow ssl=True and it means sslmode="REQUIRED" on MySQL Connector and set MYSQL_OPT_SSL_ENFORCE on MariaDB Connector. Fix #699 --- src/MySQLdb/_mysql.c | 36 ++++++++++++++++++++++++------------ src/MySQLdb/connections.py | 2 ++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index c334db66..b9ec1c12 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -391,10 +391,10 @@ enum { }; static int -_get_ssl_mode_num(char *ssl_mode) +_get_ssl_mode_num(const char *ssl_mode) { - static char *ssl_mode_list[] = { "DISABLED", "PREFERRED", - "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY" }; + static const char *ssl_mode_list[] = { + "DISABLED", "PREFERRED", "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY" }; unsigned int i; for (i=0; i < sizeof(ssl_mode_list)/sizeof(ssl_mode_list[0]); i++) { if (strcmp(ssl_mode, ssl_mode_list[i]) == 0) { @@ -414,7 +414,7 @@ _mysql_ConnectionObject_Initialize( MYSQL *conn = NULL; PyObject *conv = NULL; PyObject *ssl = NULL; - char *ssl_mode = NULL; + const char *ssl_mode = NULL; const char *key = NULL, *cert = NULL, *ca = NULL, *capath = NULL, *cipher = NULL; PyObject *ssl_keepref[5] = {NULL}; @@ -437,7 +437,7 @@ _mysql_ConnectionObject_Initialize( int read_timeout = 0; int write_timeout = 0; int compress = -1, named_pipe = -1, local_infile = -1; - int ssl_mode_num = SSLMODE_DISABLED; + int ssl_mode_num = SSLMODE_PREFERRED; char *init_command=NULL, *read_default_file=NULL, *read_default_group=NULL, @@ -470,19 +470,31 @@ _mysql_ConnectionObject_Initialize( if(t){d=PyUnicode_AsUTF8(t);ssl_keepref[n_ssl_keepref++]=t;}\ PyErr_Clear();} + char ssl_mode_set = 0; if (ssl) { - PyObject *value = NULL; - _stringsuck(ca, value, ssl); - _stringsuck(capath, value, ssl); - _stringsuck(cert, value, ssl); - _stringsuck(key, value, ssl); - _stringsuck(cipher, value, ssl); + if (PyMapping_Check(ssl)) { + PyObject *value = NULL; + _stringsuck(ca, value, ssl); + _stringsuck(capath, value, ssl); + _stringsuck(cert, value, ssl); + _stringsuck(key, value, ssl); + _stringsuck(cipher, value, ssl); + } else if (PyObject_IsTrue(ssl)) { + // Support ssl=True from mysqlclient 2.2.4. + // for compatibility with PyMySQL and mysqlclient==2.2.1&libmariadb. + ssl_mode_num = SSLMODE_REQUIRED; + ssl_mode_set = 1; + } else { + ssl_mode_num = SSLMODE_DISABLED; + ssl_mode_set = 1; + } } if (ssl_mode) { if ((ssl_mode_num = _get_ssl_mode_num(ssl_mode)) <= 0) { PyErr_SetString(_mysql_NotSupportedError, "Unknown ssl_mode specification"); return -1; } + ssl_mode_set = 1; } conn = mysql_init(&(self->connection)); @@ -531,7 +543,7 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_SSL_CIPHER, cipher); } - if (ssl_mode) { + if (ssl_mode_set) { #ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE mysql_options(&(self->connection), MYSQL_OPT_SSL_MODE, &ssl_mode_num); #else diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py index 4fa762d4..c7132293 100644 --- a/src/MySQLdb/connections.py +++ b/src/MySQLdb/connections.py @@ -134,6 +134,8 @@ class object, used to create cursors (keyword only) see the MySQL documentation for more details (mysql_ssl_set()). If this is set, and the client does not support SSL, NotSupportedError will be raised. + Since mysqlclient 2.2.4, ssl=True is alias of ssl_mode=REQUIRED + for better compatibility with PyMySQL and MariaDB. :param bool local_infile: enables LOAD LOCAL INFILE; zero disables From 9fd238b9e3105dcbed2b009a916828a38d1f0904 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 9 Feb 2024 00:35:16 +0900 Subject: [PATCH 377/396] release 2.2.4 (#701) --- HISTORY.rst | 11 +++++++++++ src/MySQLdb/release.py | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index b57251c7..3dca31cc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,14 @@ +====================== + What's new in 2.2.4 +====================== + +Release: 2024-02-09 + +* Support ``ssl=True`` in ``connect()``. (#700) + This makes better compatibility with PyMySQL and mysqlclient==2.2.1 + with libmariadb. See #698 for detail. + + ====================== What's new in 2.2.3 ====================== diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 6bc1089f..35d53e20 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.3" -version_info = (2, 2, 3, "final", 0) +__version__ = "2.2.4" +version_info = (2, 2, 4, "final", 0) From 423e45e2c5854b65e55fb0c6d9046ada93e97920 Mon Sep 17 00:00:00 2001 From: nattofriends Date: Wed, 7 Aug 2024 10:16:14 -0700 Subject: [PATCH 378/396] Support building against Percona Server builds of MySQL client library `libperconaserverclient` (#718) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f7f3d924..771b39b4 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def find_package_name(): """Get available pkg-config package name""" # Ubuntu uses mariadb.pc, but CentOS uses libmariadb.pc - packages = ["mysqlclient", "mariadb", "libmariadb"] + packages = ["mysqlclient", "mariadb", "libmariadb", "perconaserverclient"] for pkg in packages: try: cmd = f"pkg-config --exists {pkg}" From a958c5fa45d3833e9fbf31ce0c4a461782929067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sil=C3=A9n?= Date: Wed, 21 Aug 2024 13:10:56 +0300 Subject: [PATCH 379/396] add MariaDB to README and doc (#720) --- README.md | 7 ++++--- doc/user_guide.rst | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 451ce799..e679b533 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,15 @@ This project adds Python 3 support and fixed many bugs. **Do Not use Github Issue Tracker to ask help. OSS Maintainer is not free tech support** -When your question looks relating to Python rather than MySQL: +When your question looks relating to Python rather than MySQL/MariaDB: * Python mailing list [python-list](https://mail.python.org/mailman/listinfo/python-list) * Slack [pythondev.slack.com](https://pyslackers.com/web/slack) -Or when you have question about MySQL: +Or when you have question about MySQL/MariaDB: -* [MySQL Community on Slack](https://lefred.be/mysql-community-on-slack/) +* [MySQL Support](https://dev.mysql.com/support/) +* [Getting Help With MariaDB](https://mariadb.com/kb/en/getting-help-with-mariadb/) ## Install diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 8b057e08..ab33615c 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -8,8 +8,8 @@ MySQLdb User's Guide Introduction ------------ -MySQLdb is an interface to the popular MySQL -database server that provides the Python database API. +MySQLdb is an interface to the popular MySQL or MariaDB +database servers that provides the Python database API. Installation ------------ From 94cae1413d16ef437f38460ac5c96cf7e9b84725 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 8 Sep 2024 17:11:36 +0900 Subject: [PATCH 380/396] Update license metadata (#722) Use GPLv2 or later --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0ad7ae58..25cdb5ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,12 +7,12 @@ requires-python = ">=3.8" authors = [ {name = "Inada Naoki", email = "songofacandy@gmail.com"} ] -license = {text = "GNU General Public License v2 (GPLv2)"} +license = {text = "GNU General Public License v2 or later (GPLv2+)"} keywords = ["MySQL"] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Other Environment", - "License :: OSI Approved :: GNU General Public License (GPL)", + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows NT/2000", "Operating System :: OS Independent", From 3599f77524a20ae78ffd2a62d19dff2b7c2616e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:40:24 +0900 Subject: [PATCH 381/396] chore(deps): update dependency sphinx-rtd-theme to v3 (#724) --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 01406623..d2f5c5a5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,2 +1,2 @@ sphinx~=7.2 -sphinx-rtd-theme~=2.0.0 +sphinx-rtd-theme~=3.0.0 From 4bcf3e634d7c23a4cca6b5be85db127307fd861a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 02:51:43 +0900 Subject: [PATCH 382/396] chore(deps): update dependency sphinx to v8 (#716) --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index d2f5c5a5..48319f03 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,2 +1,2 @@ -sphinx~=7.2 +sphinx~=8.0 sphinx-rtd-theme~=3.0.0 From b5c6cdc9ee7bdb03ee25f0723879afa871847982 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 20 Oct 2024 16:57:14 +0900 Subject: [PATCH 383/396] ci: update test (#727) --- .github/workflows/tests.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5545b885..8156fccc 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,9 +13,9 @@ jobs: PIP_DISABLE_PIP_VERSION_CHECK: 1 strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] include: - - python-version: "3.11" + - python-version: "3.12" mariadb: 1 steps: - if: ${{ matrix.mariadb }} @@ -68,7 +68,7 @@ jobs: env: PIP_NO_PYTHON_VERSION_WARNING: 1 PIP_DISABLE_PIP_VERSION_CHECK: 1 - DJANGO_VERSION: "3.2.19" + DJANGO_VERSION: "4.2.16" steps: - name: Start MySQL run: | @@ -83,9 +83,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - # Django 3.2.9+ supports Python 3.10 - # https://docs.djangoproject.com/ja/3.2/releases/3.2/ - python-version: "3.10" + python-version: "3.12" cache: "pip" cache-dependency-path: "ci/django-requirements.txt" From 95db801658e2821ba0f6bfc1a7479704095e8446 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 20 Oct 2024 16:57:31 +0900 Subject: [PATCH 384/396] ci: update MariaDB Connector/C (#726) --- .github/workflows/windows.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index ec79ca4c..e8844932 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -10,9 +10,8 @@ jobs: build: runs-on: windows-latest env: - CONNECTOR_VERSION: "3.3.8" + CONNECTOR_VERSION: "3.4.1" steps: - - name: Cache Connector id: cache-connector uses: actions/cache@v4 @@ -63,9 +62,9 @@ jobs: - name: Build wheels working-directory: mysqlclient env: - CIBW_PROJECT_REQUIRES_PYTHON: ">=3.8" + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.9" CIBW_ARCHS: "AMD64" - CIBW_TEST_COMMAND: "python -c \"import MySQLdb; print(MySQLdb.version_info)\" " + CIBW_TEST_COMMAND: 'python -c "import MySQLdb; print(MySQLdb.version_info)" ' run: "python -m cibuildwheel --prerelease-pythons --output-dir dist" - name: Build sdist From 6eb6c2f8796fdf16c5356beb4cb888da4e46be09 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 20 Oct 2024 17:17:02 +0900 Subject: [PATCH 385/396] release 2.2.5 (#728) --- HISTORY.rst | 10 +++++++++- pyproject.toml | 1 + src/MySQLdb/release.py | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 3dca31cc..1c795bfe 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,12 @@ +====================== + What's new in 2.2.5 +====================== + +Release: 2024-10-20 + +* (Windows wheel) Update MariaDB Connector/C to 3.4.1. #726 +* (Windows wheel) Build wheels for Python 3.13. #726 + ====================== What's new in 2.2.4 ====================== @@ -633,4 +642,3 @@ ursor.fetchXXXDict() methods raise DeprecationWarning cursor.begin() is making a brief reappearence. cursor.callproc() now works, with some limitations. - diff --git a/pyproject.toml b/pyproject.toml index 25cdb5ff..19762485 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Database", "Topic :: Database :: Database Engines/Servers", ] diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 35d53e20..a1b63f6b 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.4" -version_info = (2, 2, 4, "final", 0) +__version__ = "2.2.5" +version_info = (2, 2, 5, "final", 0) From 89511eef44a9a1ba1d02358c97997ef9073572a8 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 12 Nov 2024 19:06:52 +0900 Subject: [PATCH 386/396] windows: use DEFAULT_SSL_VERIFY_SERVER_CERT=0 option (#731) --- .github/workflows/windows.yaml | 25 ++++++++++++++++---- src/MySQLdb/_mysql.c | 42 +++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e8844932..f8dbf87a 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -17,7 +17,7 @@ jobs: uses: actions/cache@v4 with: path: c:/mariadb-connector - key: mariadb-connector-c-${{ env.CONNECTOR_VERSION }}-win + key: mariadb-connector-c-${{ env.CONNECTOR_VERSION }}-win-2 - name: Download and Unzip Connector if: steps.cache-connector.outputs.cache-hit != 'true' @@ -27,15 +27,32 @@ jobs: unzip "mariadb-connector-c-${CONNECTOR_VERSION}-src.zip" -d c:/ mv "c:/mariadb-connector-c-${CONNECTOR_VERSION}-src" c:/mariadb-connector-src - - name: Build Connector + - name: make build directory if: steps.cache-connector.outputs.cache-hit != 'true' shell: cmd working-directory: c:/mariadb-connector-src run: | mkdir build - cd build - cmake -A x64 .. -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static + + - name: cmake + if: steps.cache-connector.outputs.cache-hit != 'true' + shell: cmd + working-directory: c:/mariadb-connector-src/build + run: | + cmake -A x64 .. -DCMAKE_BUILD_TYPE=Release -DCLIENT_PLUGIN_DIALOG=static -DCLIENT_PLUGIN_SHA256_PASSWORD=static -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=static -DDEFAULT_SSL_VERIFY_SERVER_CERT=0 + + - name: cmake build + if: steps.cache-connector.outputs.cache-hit != 'true' + shell: cmd + working-directory: c:/mariadb-connector-src/build + run: | cmake --build . -j 8 --config Release + + - name: cmake install + if: steps.cache-connector.outputs.cache-hit != 'true' + shell: cmd + working-directory: c:/mariadb-connector-src/build + run: | cmake -DCMAKE_INSTALL_PREFIX=c:/mariadb-connector -DCMAKE_INSTALL_COMPONENT=Development -DCMAKE_BUILD_TYPE=Release -P cmake_install.cmake - name: Checkout mysqlclient diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index b9ec1c12..1468f3e8 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -543,23 +543,30 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_SSL_CIPHER, cipher); } - if (ssl_mode_set) { #ifdef HAVE_ENUM_MYSQL_OPT_SSL_MODE + if (ssl_mode_set) { mysql_options(&(self->connection), MYSQL_OPT_SSL_MODE, &ssl_mode_num); + } #else - // MariaDB doesn't support MYSQL_OPT_SSL_MODE. - // See https://github.com/PyMySQL/mysqlclient/issues/474 - // TODO: Does MariaDB supports PREFERRED and VERIFY_CA? - // We support only two levels for now. - my_bool enforce_tls = 1; - if (ssl_mode_num >= SSLMODE_REQUIRED) { - mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&enforce_tls); - } - if (ssl_mode_num >= SSLMODE_VERIFY_CA) { - mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&enforce_tls); - } -#endif + // MariaDB doesn't support MYSQL_OPT_SSL_MODE. + // See https://github.com/PyMySQL/mysqlclient/issues/474 + // And MariDB 11.4 changed the default value of MYSQL_OPT_SSL_ENFORCE and + // MYSQL_OPT_SSL_VERIFY_SERVER_CERT to 1. + // https://github.com/mariadb-corporation/mariadb-connector-c/commit/8dffd56936df3d03eeccf47904773860a0cdeb57 + // We emulate the ssl_mode and old behavior. + my_bool my_true = 1; + my_bool my_false = 0; + if (ssl_mode_num >= SSLMODE_REQUIRED) { + mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_true); + } else { + mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_false); + } + if (ssl_mode_num >= SSLMODE_VERIFY_CA) { + mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_true); + } else { + mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_false); } +#endif if (charset) { mysql_options(&(self->connection), MYSQL_SET_CHARSET_NAME, charset); @@ -573,12 +580,9 @@ _mysql_ConnectionObject_Initialize( port, unix_socket, client_flag); Py_END_ALLOW_THREADS - if (ssl) { - int i; - for (i=0; i Date: Tue, 12 Nov 2024 20:52:23 +0900 Subject: [PATCH 387/396] support server_public_key_path option. (#744) fix #682 --- doc/user_guide.rst | 4 +++ src/MySQLdb/_mysql.c | 53 ++++++++++++++++++++++++++------------ src/MySQLdb/connections.py | 4 +++ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index ab33615c..8c76417b 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -393,6 +393,10 @@ connect(parameters...) an exception is raised. *This must be a keyword parameter.* + server_public_key_path + specifies path to a RSA public key used by caching sha2 password authentication. + See https://dev.mysql.com/doc/refman/9.0/en/caching-sha2-pluggable-authentication.html + .. _mysql_ssl_set: http://dev.mysql.com/doc/refman/en/mysql-ssl-set.html diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 1468f3e8..d6df4df8 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -36,13 +36,18 @@ PERFORMANCE OF THIS SOFTWARE. #endif #if ((MYSQL_VERSION_ID >= 50555 && MYSQL_VERSION_ID <= 50599) || \ -(MYSQL_VERSION_ID >= 50636 && MYSQL_VERSION_ID <= 50699) || \ -(MYSQL_VERSION_ID >= 50711 && MYSQL_VERSION_ID <= 50799) || \ -(MYSQL_VERSION_ID >= 80000)) && \ -!defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) + (MYSQL_VERSION_ID >= 50636 && MYSQL_VERSION_ID <= 50699) || \ + (MYSQL_VERSION_ID >= 50711 && MYSQL_VERSION_ID <= 50799) || \ + (MYSQL_VERSION_ID >= 80000)) && \ + !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) #define HAVE_ENUM_MYSQL_OPT_SSL_MODE #endif +#if defined(MARIADB_VERSION_ID) && MARIADB_VERSION_ID >= 100403 || \ + !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 50723 +#define HAVE_MYSQL_SERVER_PUBLIC_KEY +#endif + #define PY_SSIZE_T_CLEAN 1 #include "Python.h" @@ -431,7 +436,7 @@ _mysql_ConnectionObject_Initialize( "client_flag", "ssl", "ssl_mode", "local_infile", "read_timeout", "write_timeout", "charset", - "auth_plugin", + "auth_plugin", "server_public_key_path", NULL } ; int connect_timeout = 0; int read_timeout = 0; @@ -442,14 +447,15 @@ _mysql_ConnectionObject_Initialize( *read_default_file=NULL, *read_default_group=NULL, *charset=NULL, - *auth_plugin=NULL; + *auth_plugin=NULL, + *server_public_key_path=NULL; self->converter = NULL; self->open = false; self->reconnect = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|ssssisOiiisssiOsiiiss:connect", + "|ssssisOiiisssiOsiiisss:connect", kwlist, &host, &user, &passwd, &db, &port, &unix_socket, &conv, @@ -462,10 +468,19 @@ _mysql_ConnectionObject_Initialize( &read_timeout, &write_timeout, &charset, - &auth_plugin + &auth_plugin, + &server_public_key_path )) return -1; +#ifndef HAVE_MYSQL_SERVER_PUBLIC_KEY + if (server_public_key_path) { + PyErr_SetString(_mysql_NotSupportedError, "server_public_key_path is not supported"); + return -1; + } +#endif + // For compatibility with PyPy, we need to keep strong reference + // to unicode objects until we use UTF8. #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ if(t){d=PyUnicode_AsUTF8(t);ssl_keepref[n_ssl_keepref++]=t;}\ PyErr_Clear();} @@ -542,6 +557,10 @@ _mysql_ConnectionObject_Initialize( mysql_options(&(self->connection), MYSQL_OPT_SSL_CAPATH, capath); mysql_options(&(self->connection), MYSQL_OPT_SSL_CIPHER, cipher); } + for (int i=0 ; i= SSLMODE_REQUIRED) { - mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_true); + mysql_options(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_true); } else { - mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_false); + mysql_options(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_false); } if (ssl_mode_num >= SSLMODE_VERIFY_CA) { - mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_true); + mysql_options(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_true); } else { - mysql_optionsv(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_false); + mysql_options(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_false); } #endif @@ -574,17 +593,17 @@ _mysql_ConnectionObject_Initialize( if (auth_plugin) { mysql_options(&(self->connection), MYSQL_DEFAULT_AUTH, auth_plugin); } +#ifdef HAVE_MYSQL_SERVER_PUBLIC_KEY + if (server_public_key_path) { + mysql_options(&(self->connection), MYSQL_SERVER_PUBLIC_KEY, server_public_key_path); + } +#endif Py_BEGIN_ALLOW_THREADS conn = mysql_real_connect(&(self->connection), host, user, passwd, db, port, unix_socket, client_flag); Py_END_ALLOW_THREADS - for (int i=0; i Date: Wed, 13 Nov 2024 13:22:52 +0900 Subject: [PATCH 388/396] release v2.2.6 (#745) --- HISTORY.rst | 13 +++++++++++++ pyproject.toml | 1 - src/MySQLdb/release.py | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 1c795bfe..bc95c774 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,16 @@ +====================== + What's new in 2.2.6 +====================== + +Release: 2024-11-12 + +* MariaDB Connector/C 3.4 and MairaDB 11.4 enabled SSL and CA verification by default. + It affected 2.2.5 windows wheel. This release disables SSL and CA verification by default. (#731) + +* Add ``server_public_key_path`` option. It is needed to connect MySQL server with + ``sha256_password`` or ``caching_sha2_password`` authentication plugin without + secure connection. (#744) + ====================== What's new in 2.2.5 ====================== diff --git a/pyproject.toml b/pyproject.toml index 19762485..d786f336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "mysqlclient" -# version = "2.2.0dev0" description = "Python interface to MySQL" readme = "README.md" requires-python = ">=3.8" diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index a1b63f6b..0b168441 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.5" -version_info = (2, 2, 5, "final", 0) +__version__ = "2.2.6" +version_info = (2, 2, 6, "final", 0) From afdd93a1c5c0c40d10951adc26bb3ceae63502be Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 13 Nov 2024 14:07:00 +0900 Subject: [PATCH 389/396] ci: fix django test (#746) --- ci/test_mysql.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/test_mysql.py b/ci/test_mysql.py index 9417fc9f..498be7cf 100644 --- a/ci/test_mysql.py +++ b/ci/test_mysql.py @@ -39,3 +39,5 @@ ] DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +USE_TZ = False From 49e44c5b469a5abf81731b45ad0eb257376a718e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:04:55 +0900 Subject: [PATCH 390/396] chore(deps): update codecov/codecov-action action to v5 (#747) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8156fccc..cf784c78 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -59,7 +59,7 @@ jobs: run: | pytest --cov=MySQLdb tests - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 django-test: name: "Run Django LTS test suite" From 207ac1a948a3e4ae31ceadb78d08bf83bd2f2520 Mon Sep 17 00:00:00 2001 From: Cristi Fati Date: Sat, 14 Dec 2024 04:06:06 +0200 Subject: [PATCH 391/396] windows: add MariaDB include paths (#749) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 771b39b4..9a1d26b8 100644 --- a/setup.py +++ b/setup.py @@ -109,6 +109,7 @@ def get_config_win32(options): ] include_dirs = [ os.path.join(connector, "include", "mariadb"), + os.path.join(connector, "include", "mysql"), os.path.join(connector, "include"), ] From 8103651bffbf0ee890d2731d4e1fae1f80a50661 Mon Sep 17 00:00:00 2001 From: Mikhail Chinkov Date: Sun, 5 Jan 2025 05:17:09 +0100 Subject: [PATCH 392/396] add database name as the Connection attribute (#752) This change adds `self.database` as the Connection attribute displaying the database name from connection parameters. This is very useful for otel dbapi tracer. --- src/MySQLdb/connections.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py index 73c95e0f..c3575454 100644 --- a/src/MySQLdb/connections.py +++ b/src/MySQLdb/connections.py @@ -204,6 +204,8 @@ class object, used to create cursors (keyword only) if type(k) is not int # noqa: E721 } + self.database = kwargs2.get("database", "") + self._server_version = tuple( [numeric_part(n) for n in self.get_server_info().split(".")[:2]] ) From 2226fc471930a73f59985ab48b6f54e76c865eac Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 10 Jan 2025 20:18:31 +0900 Subject: [PATCH 393/396] support opentelemetry-instrumentation (#753) --- src/MySQLdb/connections.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py index c3575454..5bcfc937 100644 --- a/src/MySQLdb/connections.py +++ b/src/MySQLdb/connections.py @@ -196,20 +196,18 @@ class object, used to create cursors (keyword only) # PEP-249 requires autocommit to be initially off autocommit = kwargs2.pop("autocommit", False) + self._set_attributes(*args, **kwargs2) super().__init__(*args, **kwargs2) + self.cursorclass = cursorclass self.encoders = { k: v for k, v in conv.items() if type(k) is not int # noqa: E721 } - - self.database = kwargs2.get("database", "") - self._server_version = tuple( [numeric_part(n) for n in self.get_server_info().split(".")[:2]] ) - self.encoding = "ascii" # overridden in set_character_set() if not charset: @@ -240,6 +238,21 @@ class object, used to create cursors (keyword only) self.autocommit(autocommit) self.messages = [] + def _set_attributes(self, host=None, user=None, password=None, database="", port=3306, + unix_socket=None, **kwargs): + """set some attributes for otel""" + if unix_socket and not host: + host = "localhost" + # support opentelemetry-instrumentation-dbapi + self.host = host + # _mysql.Connection provides self.port + self.user = user + self.database = database + # otel-inst-mysqlclient uses db instead of database. + self.db = database + # NOTE: We have not supported semantic conventions yet. + # https://opentelemetry.io/docs/specs/semconv/database/sql/ + def __enter__(self): return self From 2076d161e2f09f3fb99e00134c9f415063924ad9 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 10 Jan 2025 20:45:32 +0900 Subject: [PATCH 394/396] release v2.2.7 (#754) --- HISTORY.rst | 9 +++++++++ src/MySQLdb/release.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index bc95c774..66470541 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,3 +1,12 @@ +====================== + What's new in 2.2.7 +====================== + +Release: 2025-01-10 + +* Add ``user``, ``host``, ``database``, and ``db`` attributes to ``Connection``. + opentelemetry-instrumentation-(dbapi|mysqlclient) use them. (#753) + ====================== What's new in 2.2.6 ====================== diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py index 0b168441..234d9958 100644 --- a/src/MySQLdb/release.py +++ b/src/MySQLdb/release.py @@ -1,3 +1,3 @@ __author__ = "Inada Naoki " -__version__ = "2.2.6" -version_info = (2, 2, 6, "final", 0) +__version__ = "2.2.7" +version_info = (2, 2, 7, "final", 0) From 5b358664e5581571b7a21ee85829ca51869b23e0 Mon Sep 17 00:00:00 2001 From: RasmusKard Date: Sun, 23 Feb 2025 12:37:40 +0200 Subject: [PATCH 395/396] support local_infile_dir option (#755) Adds support for `MYSQL_OPT_LOAD_DATA_LOCAL_DIR` in `mysql_options()` as the kwarg `local_infile_dir`. A bit safer alternative than `local_infile` for MySQL versions >= 8.0.21. --- doc/user_guide.rst | 11 +++++++++++ src/MySQLdb/_mysql.c | 27 +++++++++++++++++++++++---- src/MySQLdb/connections.py | 8 +++++++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 8c76417b..391b162f 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -396,6 +396,17 @@ connect(parameters...) server_public_key_path specifies path to a RSA public key used by caching sha2 password authentication. See https://dev.mysql.com/doc/refman/9.0/en/caching-sha2-pluggable-authentication.html + + local_infile + sets ``MYSQL_OPT_LOCAL_INFILE`` in ``mysql_options()`` enabling LOAD LOCAL INFILE from any path; zero disables; + + *This must be a keyword parameter.* + + local_infile_dir + sets ``MYSQL_OPT_LOAD_DATA_LOCAL_DIR`` in ``mysql_options()`` enabling LOAD LOCAL INFILE from any path; + if ``local_infile`` is set to ``True`` then this is ignored; + + *This must be a keyword parameter.* .. _mysql_ssl_set: http://dev.mysql.com/doc/refman/en/mysql-ssl-set.html diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index d6df4df8..cd95b641 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -48,6 +48,10 @@ PERFORMANCE OF THIS SOFTWARE. #define HAVE_MYSQL_SERVER_PUBLIC_KEY #endif +#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80021 +#define HAVE_MYSQL_OPT_LOCAL_INFILE_DIR +#endif + #define PY_SSIZE_T_CLEAN 1 #include "Python.h" @@ -436,7 +440,7 @@ _mysql_ConnectionObject_Initialize( "client_flag", "ssl", "ssl_mode", "local_infile", "read_timeout", "write_timeout", "charset", - "auth_plugin", "server_public_key_path", + "auth_plugin", "server_public_key_path", "local_infile_dir", NULL } ; int connect_timeout = 0; int read_timeout = 0; @@ -448,14 +452,15 @@ _mysql_ConnectionObject_Initialize( *read_default_group=NULL, *charset=NULL, *auth_plugin=NULL, - *server_public_key_path=NULL; + *server_public_key_path=NULL, + *local_infile_dir=NULL; self->converter = NULL; self->open = false; self->reconnect = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|ssssisOiiisssiOsiiisss:connect", + "|ssssisOiiisssiOsiiissss:connect", kwlist, &host, &user, &passwd, &db, &port, &unix_socket, &conv, @@ -469,7 +474,8 @@ _mysql_ConnectionObject_Initialize( &write_timeout, &charset, &auth_plugin, - &server_public_key_path + &server_public_key_path, + &local_infile_dir )) return -1; @@ -479,6 +485,13 @@ _mysql_ConnectionObject_Initialize( return -1; } #endif + +#ifndef HAVE_MYSQL_OPT_LOCAL_INFILE_DIR + if (local_infile_dir) { + PyErr_SetString(_mysql_NotSupportedError, "local_infile_dir is not supported"); + return -1; + } +#endif // For compatibility with PyPy, we need to keep strong reference // to unicode objects until we use UTF8. #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ @@ -599,6 +612,12 @@ _mysql_ConnectionObject_Initialize( } #endif +#ifdef HAVE_MYSQL_OPT_LOCAL_INFILE_DIR + if (local_infile_dir) { + mysql_options(&(self->connection), MYSQL_OPT_LOAD_DATA_LOCAL_DIR, local_infile_dir); + } +#endif + Py_BEGIN_ALLOW_THREADS conn = mysql_real_connect(&(self->connection), host, user, passwd, db, port, unix_socket, client_flag); diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py index 5bcfc937..a61aaaed 100644 --- a/src/MySQLdb/connections.py +++ b/src/MySQLdb/connections.py @@ -142,7 +142,13 @@ class object, used to create cursors (keyword only) See https://dev.mysql.com/doc/refman/9.0/en/caching-sha2-pluggable-authentication.html :param bool local_infile: - enables LOAD LOCAL INFILE; zero disables + sets ``MYSQL_OPT_LOCAL_INFILE`` in ``mysql_options()`` enabling LOAD LOCAL INFILE from any path; zero disables; + + :param str local_infile_dir: + sets ``MYSQL_OPT_LOAD_DATA_LOCAL_DIR`` in ``mysql_options()`` enabling LOAD LOCAL INFILE from any path; + if ``local_infile`` is set to ``True`` then this is ignored; + + supported for mysql version >= 8.0.21 :param bool autocommit: If False (default), autocommit is disabled. From e54e8612957e0d74dafe5d186ba1247a612c81bd Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 23 Feb 2025 19:58:05 +0900 Subject: [PATCH 396/396] update readthedocs conf (#759) --- .readthedocs.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 0b288620..75d7a389 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,9 +1,12 @@ version: 2 +sphinx: + configuration: doc/conf.py + build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.13" apt_packages: - default-libmysqlclient-dev