From c67dbd41f13f5302120ad40fee94ea6c7ffc2bfc Mon Sep 17 00:00:00 2001 From: Jason Fried Date: Wed, 5 Feb 2020 03:38:25 -0800 Subject: [PATCH 001/170] 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 002/170] 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 003/170] 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 004/170] 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 005/170] 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 006/170] 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 007/170] 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 008/170] 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 009/170] 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 010/170] 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 011/170] 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 012/170] 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 013/170] 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 014/170] 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 015/170] 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 016/170] 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 017/170] 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 018/170] 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 019/170] 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 020/170] 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 021/170] 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 022/170] 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 023/170] 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 024/170] 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 025/170] 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 026/170] 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 027/170] 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 028/170] 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 029/170] 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 030/170] 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 031/170] 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 032/170] 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 033/170] 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 034/170] 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 035/170] 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 036/170] 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 037/170] 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 038/170] 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 039/170] 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 040/170] 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 041/170] 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 042/170] 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 043/170] 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 044/170] 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 045/170] 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 046/170] 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 047/170] 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 048/170] 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 049/170] 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 050/170] 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 051/170] 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 052/170] 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 053/170] 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 054/170] 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 055/170] 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 056/170] 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 057/170] 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 058/170] 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 059/170] 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 060/170] _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 061/170] 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 062/170] 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 063/170] 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 064/170] 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 065/170] 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 066/170] 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 067/170] 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 068/170] 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 069/170] 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 070/170] 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 071/170] 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 072/170] 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 073/170] 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 074/170] 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 075/170] 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 076/170] 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 077/170] 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 078/170] 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 079/170] 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 080/170] 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 081/170] 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 082/170] 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 083/170] 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 084/170] 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 085/170] 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 086/170] 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 087/170] 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 088/170] 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 089/170] 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 090/170] 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 091/170] 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 092/170] 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 093/170] 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 094/170] 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 095/170] 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 096/170] 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 097/170] 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 098/170] 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 099/170] 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 100/170] 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 101/170] 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 102/170] 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 103/170] 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 104/170] 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 105/170] 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 106/170] 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 107/170] 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 108/170] 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 109/170] 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 110/170] 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 111/170] 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 112/170] 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 113/170] 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 114/170] 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 115/170] 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 116/170] 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 117/170] 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 118/170] 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 119/170] 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 120/170] 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 121/170] 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 122/170] 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 123/170] 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 124/170] 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 125/170] 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 126/170] 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 127/170] 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 128/170] 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 129/170] 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 130/170] 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 131/170] 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 132/170] 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 133/170] 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 134/170] 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 135/170] 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 136/170] 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 137/170] 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 138/170] 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 139/170] 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 140/170] 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 141/170] 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 142/170] 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 143/170] 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 144/170] 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 145/170] 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 146/170] 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 147/170] 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 148/170] 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 149/170] 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 150/170] 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 151/170] 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 152/170] 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 153/170] 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 154/170] 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 155/170] 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 156/170] 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 157/170] 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 158/170] 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 159/170] 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 160/170] 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 161/170] 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 162/170] 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 163/170] 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 164/170] 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 165/170] 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 166/170] 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 167/170] 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 168/170] 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 169/170] 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 170/170] 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