diff --git a/.github/DISCUSSION_TEMPLATE/build-issue.yml b/.github/DISCUSSION_TEMPLATE/build-issue.yml new file mode 100644 index 00000000..c2e93df4 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/build-issue.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 + + + + diff --git a/.github/DISCUSSION_TEMPLATE/issue-report.yml b/.github/DISCUSSION_TEMPLATE/issue-report.yml new file mode 100644 index 00000000..6724fcf8 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/issue-report.yml @@ -0,0 +1,93 @@ +body: + - type: markdown + attributes: + value: | + 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. + + 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. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..9f1273ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,12 @@ +contact_links: + - name: Failed to build + about: Ask help for build error. + url: "https://github.com/PyMySQL/mysqlclient/discussions/new?category=build-issue" + + - 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" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..95a95ac3 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,15 @@ +name: Lint + +on: + push: + branches: ["main"] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: pipx install ruff + - run: ruff check src/ + - run: ruff format src/ diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..cf784c78 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,108 @@ +name: Test + +on: + push: + branches: ["main"] + pull_request: + +jobs: + 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", "3.12", "3.13"] + include: + - python-version: "3.12" + mariadb: 1 + steps: + - 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@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "requirements.txt" + allow-prereleases: true + + - name: Install mysqlclient + run: | + pip install -v . + + - name: Install test dependencies + run: | + pip install -r requirements.txt + + - name: Run tests + env: + TESTDB: actions.cnf + run: | + pytest --cov=MySQLdb tests + + - uses: codecov/codecov-action@v5 + + 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: "4.2.16" + 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@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + 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 new file mode 100644 index 00000000..f8dbf87a --- /dev/null +++ b/.github/workflows/windows.yaml @@ -0,0 +1,97 @@ +name: Build windows wheels + +on: + push: + branches: ["main", "ci"] + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + env: + CONNECTOR_VERSION: "3.4.1" + steps: + - name: Cache Connector + id: cache-connector + uses: actions/cache@v4 + with: + path: c:/mariadb-connector + key: mariadb-connector-c-${{ env.CONNECTOR_VERSION }}-win-2 + + - 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: make build directory + if: steps.cache-connector.outputs.cache-hit != 'true' + shell: cmd + working-directory: c:/mariadb-connector-src + run: | + mkdir build + + - 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 + uses: actions/checkout@v4 + with: + 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 + + - uses: actions/setup-python@v5 + - name: Install cibuildwheel + run: python -m pip install cibuildwheel + - name: Build wheels + working-directory: mysqlclient + env: + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.9" + CIBW_ARCHS: "AMD64" + 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@v4 + with: + name: win-wheels + path: mysqlclient/dist/*.* 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/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..75d7a389 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +version: 2 + +sphinx: + configuration: doc/conf.py + +build: + os: ubuntu-22.04 + tools: + python: "3.13" + + apt_packages: + - default-libmysqlclient-dev + - build-essential + +python: + install: + - requirements: doc/requirements.txt + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6f479e52..00000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -sudo: false -language: python -python: "3.5" - -cache: pip -install: - - pip install tox - -env: - matrix: - - TOX_ENV=py26 - - TOX_ENV=py27 - - TOX_ENV=pypy - - TOX_ENV=py33 - - TOX_ENV=py34 - - TOX_ENV=py35 - global: - - TESTDB=travis.cnf -before_script: - - "mysql --help" - - "mysql --print-defaults" - - "mysql -e 'create database mysqldb_test charset utf8mb4;'" - -script: tox -e $TOX_ENV - - -# vim: sw=4 ts=4 sts=4 diff --git a/HISTORY b/HISTORY deleted file mode 100644 index 97ffe20d..00000000 --- a/HISTORY +++ /dev/null @@ -1,276 +0,0 @@ -===================== - What's new in 1.3.7 -===================== - -Support link args other than '-L' and '-l' from mysql_config. - -Missing value for column without default value cause IntegrityError. (#33) - -Support BIT type. (#38) - -More tests for date and time columns. (#41) - -Fix calling .execute() method for closed cursor cause TypeError. (#37) - -Improve peformance to parse date. (#43) - -Support geometry types (#49) - -Fix warning while multi statement cause ProgrammingError. (#48) - - -===================== - What's new in 1.3.6 -===================== - -Fix escape_string() doesn't work. - -Remove `Cursor.__del__` to fix uncollectable circular reference on Python 3.3. - -Add context manager support to `Cursor`. It automatically closes cursor on `__exit__`. - -.. code-block:: - - with conn.cursor() as cur: - cur.execute("SELECT 1+1") - print(cur.fetchone()) - # cur is now closed - - -===================== - What's new in 1.3.5 -===================== - -Fix TINYBLOB, MEDIUMBLOB and LONGBLOB are treated as string and decoded -to unicode or cause UnicodeError. - -Fix aware datetime is formatted with timezone offset (e.g. "+0900"). - - -===================== - What's new in 1.3.4 -===================== - -* Remove compiler warnings. -* Fix compile error when using libmariadbclient. -* Fix GIL deadlock while dealloc. - -===================== - What's new in 1.3.3 -===================== - -* Fix exception reraising doesn't work. - -===================== - What's new in 1.3.2 -===================== - -* Add send_query() and read_query_result() method to low level connection. -* Add waiter option. - - -===================== - What's new in 1.3.1 -===================== - -This is a first fork of MySQL-python. -Now named "mysqlclient" - -* Support Python 3 -* Add autocommit option -* Support microsecond in datetime field. - - -===================== - What's new in 1.2.4 -===================== - -final -===== - -No changes. - - -rc 1 -==== - -Fixed a dangling reference to the old types module. - - -beta 5 -====== - -Another internal fix for handling remapped character sets. - -_mysql.c was broken for the case where read_timeout was *not* available. (Issue #6) - -Documentation was converted to sphinx but there is a lot of cleanup left to do. - - -beta 4 -====== - -Added support for the MySQL read_timeout option. Contributed by -Jean Schurger (jean@schurger.org). - -Added a workaround so that the MySQL character set utf8mb4 works with Python; utf8 is substituted -on the Python side. - - -beta 3 -====== - -Unified test database configuration, and set up CI testing with Travis. - -Applied several patches from André Malo (ndparker@users.sf.net) which fix some issues -with exception handling and reference counting and TEXT/BLOB conversion. - - -beta 2 -====== - -Reverted an accidental change in the exception format. (issue #1) - -Reverted some raise statements so that they will continue to work with Python < 2.6 - - -beta 1 -====== - -A lot of work has been done towards Python 3 compatibility, and avoiding warnings with Python 2.7. -This includes import changes, converting dict.has_kay(k) to k in dict, updating some test suite methods, etc. - -Due to the difficulties of supporting Python 3 and Python < 2.7, 1.2.4 will support Python 2.4 though 2.7. -1.3.0 will support Python 3 and Python 2.7 and 2.6. - -MySQLdb-2.0 is instead going to become moist-1.0. See https://github.com/farcepest/moist - -The Windows build has been simplified, and I plan to correct pre-built i386 packages built -against the python.org Python-2.7 package and MySQL Connector/C-6.0. Contact me if you -need ia64 packages. - -The connection's cursorclass (if not default) was being lost on reconnect. - -Newer versions of MySQL don't use OpenSSL and therefore don't have HAVE_SSL defined, but they do have -a different SSL library. Fixed this so SSL support would be enabled in this case. - -The regex that looked for SQL INSERT statement and VALUES in cursor.executemany() was made case-insensitive -again. - - -===================== - What's new in 1.2.3 -===================== - -ez_setup.py has been update to include various fixes that affect the build. - -Better Python version and dependency detection as well as eliminate exception -warnings under Python 2.6. - -Eliminated memory leaks related to Unicode and failed connections. - -Corrected connection .escape() functionality. - -Miscellaneous cleanups and and expanded testing suite to ensure ongoing release -quality. - -===================== - What's new in 1.2.2 -===================== - -The build system has been completely redone and should now build -on Windows without any patching; uses setuptools. - -Added compatibility for Python 2.5, including support for with statement. - -connection.ping() now takes an optional boolean argument which can -enable (or disable) automatic reconnection. - -Support returning SET columns as Python sets was removed due to an -API bug in MySQL; corresponding test removed. - -Added a test for single-character CHAR columns. - -BLOB columns are now returned as Python strings instead of byte arrays. - -BINARY character columns are always returned as Python strings, and not -unicode. - -Fixed a bug introduced in 1.2.1 where the new SHOW WARNINGS support broke -SSCursor. - -Only encode the query (convert to a string) when it is a unicode instance; -re-encoding encoded strings would break things. - -Make a deep copy of conv when connecting, since it can be modified. - -Added support for new VARCHAR and BIT column types. - -DBAPISet objects were broken, but nobody noticed. - - -======================== - What's new in 1.2.1_p2 -======================== - -There are some minor build fixes which probably only affect MySQL -older than 4.0. - -If you had MySQL older than 4.1, the new charset and sql_mode -parameters didn't work right. In fact, it was impossible to create -a connection due to the charset problem. - -If you are using MySQL-4.1 or newer, there is no practical difference -between 1.2.1 and 1.2.1_p2, and you don't need to upgrade. - - -===================== - What's new in 1.2.1 -===================== - -Switched to Subversion. Was going to do this for 1.3, but a -SourceForge CVS outage has forced the issue. - -Mapped a lot of new 4.1 and 5.0 error codes to Python exceptions - -Added an API call for mysql_set_character_set(charset) (MySQL > 5.0.7) - -Added an API call for mysql_get_character_set_info() (MySQL > 5.0.10) - -Revamped the build system. Edit site.cfg if necessary (probably not -in most cases) - -Python-2.3 is now the minimum version. - -Dropped support for mx.Datetime and stringtimes; always uses Python -datetime module now. - -Improved unit tests - -New connect() options: -* charset: sets character set, implies use_unicode -* sql_mode: sets SQL mode (i.e. ANSI, etc.; see MySQL docs) - -When using MySQL-4.1 or newer, enables MULTI_STATEMENTS - -When using MySQL-5.0 or newer, enables MULTI_RESULTS - -When using MySQL-4.1 or newer, more detailed warning messages -are produced - -SET columns returned as Python Set types; you can pass a Set as -a parameter to cursor.execute(). - -Support for the new MySQL-5.0 DECIMAL implementation - -Support for Python Decimal type - -Some use of weak references internally. Cursors no longer leak -if you don't close them. Connections still do, unfortunately. - -ursor.fetchXXXDict() methods raise DeprecationWarning - -cursor.begin() is making a brief reappearence. - -cursor.callproc() now works, with some limitations. - diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 00000000..66470541 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,666 @@ +====================== + 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 +====================== + +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 +====================== + +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 +====================== + +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 +====================== + +Release: 2024-02-04 + +* Fix ``Connection.kill()`` method that broken in 2.2.2. (#689) + + +====================== + 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 +====================== + +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 +====================== + +Release: 2023-06-22 + +* 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 +====================== + +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 +====================== + +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 + 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 +====================== + +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 +====================== + +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 +====================== + +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 +====================== + +Release: 2020-07-02 + +* 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. + + +====================== + What's new in 1.4.6 +====================== + +Release: 2019-11-21 + +* The ``cp1252`` encoding is used when charset is "latin1". (#390) + +====================== + What's new in 1.4.5 +====================== + +Release: 2019-11-06 + +* The ``auth_plugin`` option is added. (#389) + + +====================== + What's new in 1.4.4 +====================== + +Release: 2019-08-12 + +* ``charset`` option is passed to ``mysql_options(mysql, MYSQL_SET_CHARSET_NAME, charset)`` + before ``mysql_real_connect`` is called. + This avoid extra ``SET NAMES `` query when creating connection. + + +====================== + What's new in 1.4.3 +====================== + +Release: 2019-08-09 + +* ``--static`` build supports ``libmariadbclient.a`` +* Try ``mariadb_config`` when ``mysql_config`` is not found +* 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) +* Fixed many circular references are created in ``Cursor.executemany()``. (#375) + + +====================== + What's new in 1.4.2 +====================== + +Release: 2019-02-08 + +* Fix Django 1.11 compatibility. (#327) + mysqlclient 1.5 will not support Django 1.11. It is not because + mysqlclient will break backward compatibility, but Django used + unsupported APIs and Django 1.11 don't fix bugs including + compatibility issues. + +====================== + What's new in 1.4.1 +====================== + +Release: 2019-01-19 + +* Fix dict parameter support (#323, regression of 1.4.0) + +====================== + What's new in 1.4.0 +====================== + +Release: 2019-01-18 + +* Dropped Python 3.4 support. + +* Removed ``threadsafe`` and ``embedded`` build options. + +* Remove some deprecated cursor classes and methods. + +* ``_mysql`` and ``_mysql_exceptions`` modules are moved under + ``MySQLdb`` package. (#293) + +* Remove ``errorhandler`` from Connection and Cursor classes. + +* Remove context manager API from Connection. It was for transaction. + New context manager API for closing connection will be added in future version. + +* Remove ``waiter`` option from Connection. + +* Remove ``escape_sequence``, and ``escape_dict`` methods from Connection class. + +* Remove automatic MySQL warning checking. + +* Drop support for MySQL Connector/C with MySQL<5.1.12. + +* Remove ``_mysql.NULL`` constant. + +* Remove ``_mysql.thread_safe()`` function. + +* Support non-ASCII field name with non-UTF-8 connection encoding. (#210) + +* Optimize decoding speed of string and integer types. + +* Remove ``MySQLdb.constants.REFRESH`` module. + +* Remove support for old datetime format for MySQL < 4.1. + +* Fix wrong errno is raised when ``mysql_real_connect`` is failed. (#316) + + +====================== + What's new in 1.3.14 +====================== + +Release: 2018-12-04 + +* Support static linking of MariaDB Connector/C (#265) + +* Better converter for Decimal and Float (#267, #268, #273, #286) + +* Add ``Connection._get_native_connection`` for XTA project (#269) + +* Fix SEGV on MariaDB Connector/C when some methods of ``Connection`` + objects are called after ``Connection.close()`` is called. (#270, #272, #276) + See https://jira.mariadb.org/browse/CONC-289 + +* Fix ``Connection.client_flag`` (#266) + +* Fix SSCursor may raise same exception twice (#282) + + * This removed ``Cursor._last_executed`` which was duplicate of ``Cursor._executed``. + Both members are private. So this type of changes are not documented in changelog + generally. But Django used the private member for ``last_executed_query`` implementation. + If you use the method the method directly or indirectly, this version will break + your application. See https://code.djangoproject.com/ticket/30013 + +* ``waiter`` option is now deprecated. (#285) + +* Fixed SSL support is not detected when built with MySQL < 5.1 (#291) + + +====================== + What's new in 1.3.13 +====================== + +Support build with MySQL 8 + +Fix decoding tiny/medium/long blobs (#215) + +Remove broken row_seek() and row_tell() APIs (#220) + +Reduce callproc roundtrip time (#223) + + +====================== + What's new in 1.3.12 +====================== + +Fix tuple argument again (#201) + +InterfaceError is raised when Connection.query() is called for closed connection (#202) + +====================== + What's new in 1.3.11 +====================== + +Support MariaDB 10.2 client library (#197, #177, #200) + +Add NEWDECIMAL to the NUMBER DBAPISet (#167) + +Allow bulk insert which no space around `VALUES` (#179) + +Fix leak of `connection->converter`. (#182) + +Support error `numbers > CR_MAX_ERROR` (#188) + +Fix tuple argument support (#145) + + +====================== + What's new in 1.3.10 +====================== + +Added `binary_prefix` option (disabled by default) to support +`_binary` prefix again. (#134) + +Fix SEGV of `_mysql.result()` when argument's type is unexpected. (#138) + +Deprecate context interface of Connection object. (#149) + +Don't use workaround of `bytes.decode('ascii', 'surrogateescape')` on Python 3.6+. (#150) + + +===================== + What's new in 1.3.9 +===================== + +Revert adding `_binary` prefix for bytes/bytearray parameter. It broke backward compatibility. + +Fix Windows compile error on MSVC. + + +===================== + What's new in 1.3.8 +===================== + +Update error constants (#113) + +Use `_binary` prefix for bytes/bytearray parameters (#106) + +Use mysql_real_escape_string_quote() if exists (#109) + +Better Warning propagation (#101) + +Fix conversion error when mysql_affected_rows returns -1 + +Fix Cursor.callproc may raise TypeError (#90, #91) + +connect() supports the 'database' and 'password' keyword arguments. + +Fix accessing dangling pointer when using ssl (#78) + +Accept %% in Cursor.executemany (#83) + +Fix warning that caused TypeError on Python 3 (#68) + +===================== + What's new in 1.3.7 +===================== + +Support link args other than '-L' and '-l' from mysql_config. + +Missing value for column without default value cause IntegrityError. (#33) + +Support BIT type. (#38) + +More tests for date and time columns. (#41) + +Fix calling .execute() method for closed cursor cause TypeError. (#37) + +Improve performance to parse date. (#43) + +Support geometry types (#49) + +Fix warning while multi statement cause ProgrammingError. (#48) + + +===================== + What's new in 1.3.6 +===================== + +Fix escape_string() doesn't work. + +Remove `Cursor.__del__` to fix uncollectable circular reference on Python 3.3. + +Add context manager support to `Cursor`. It automatically closes cursor on `__exit__`. + +.. code-block:: + + with conn.cursor() as cur: + cur.execute("SELECT 1+1") + print(cur.fetchone()) + # cur is now closed + + +===================== + What's new in 1.3.5 +===================== + +Fix TINYBLOB, MEDIUMBLOB and LONGBLOB are treated as string and decoded +to unicode or cause UnicodeError. + +Fix aware datetime is formatted with timezone offset (e.g. "+0900"). + + +===================== + What's new in 1.3.4 +===================== + +* Remove compiler warnings. +* Fix compile error when using libmariadbclient. +* Fix GIL deadlock while dealloc. + +===================== + What's new in 1.3.3 +===================== + +* Fix exception reraising doesn't work. + +===================== + What's new in 1.3.2 +===================== + +* Add send_query() and read_query_result() method to low level connection. +* Add waiter option. + + +===================== + What's new in 1.3.1 +===================== + +This is a first fork of MySQL-python. +Now named "mysqlclient" + +* Support Python 3 +* Add autocommit option +* Support microsecond in datetime field. + + +===================== + What's new in 1.2.4 +===================== + +final +===== + +No changes. + + +rc 1 +==== + +Fixed a dangling reference to the old types module. + + +beta 5 +====== + +Another internal fix for handling remapped character sets. + +`_mysql.c` was broken for the case where read_timeout was *not* available. (Issue #6) + +Documentation was converted to sphinx but there is a lot of cleanup left to do. + + +beta 4 +====== + +Added support for the MySQL read_timeout option. Contributed by +Jean Schurger (jean@schurger.org). + +Added a workaround so that the MySQL character set utf8mb4 works with Python; utf8 is substituted +on the Python side. + + +beta 3 +====== + +Unified test database configuration, and set up CI testing with Travis. + +Applied several patches from André Malo (ndparker@users.sf.net) which fix some issues +with exception handling and reference counting and TEXT/BLOB conversion. + + +beta 2 +====== + +Reverted an accidental change in the exception format. (issue #1) + +Reverted some raise statements so that they will continue to work with Python < 2.6 + + +beta 1 +====== + +A lot of work has been done towards Python 3 compatibility, and avoiding warnings with Python 2.7. +This includes import changes, converting dict.has_kay(k) to k in dict, updating some test suite methods, etc. + +Due to the difficulties of supporting Python 3 and Python < 2.7, 1.2.4 will support Python 2.4 though 2.7. +1.3.0 will support Python 3 and Python 2.7 and 2.6. + +MySQLdb-2.0 is instead going to become moist-1.0. See https://github.com/farcepest/moist + +The Windows build has been simplified, and I plan to correct pre-built i386 packages built +against the python.org Python-2.7 package and MySQL Connector/C-6.0. Contact me if you +need ia64 packages. + +The connection's cursorclass (if not default) was being lost on reconnect. + +Newer versions of MySQL don't use OpenSSL and therefore don't have HAVE_SSL defined, but they do have +a different SSL library. Fixed this so SSL support would be enabled in this case. + +The regex that looked for SQL INSERT statement and VALUES in cursor.executemany() was made case-insensitive +again. + + +===================== + What's new in 1.2.3 +===================== + +ez_setup.py has been update to include various fixes that affect the build. + +Better Python version and dependency detection as well as eliminate exception +warnings under Python 2.6. + +Eliminated memory leaks related to Unicode and failed connections. + +Corrected connection .escape() functionality. + +Miscellaneous cleanups and and expanded testing suite to ensure ongoing release +quality. + +===================== + What's new in 1.2.2 +===================== + +The build system has been completely redone and should now build +on Windows without any patching; uses setuptools. + +Added compatibility for Python 2.5, including support for with statement. + +connection.ping() now takes an optional boolean argument which can +enable (or disable) automatic reconnection. + +Support returning SET columns as Python sets was removed due to an +API bug in MySQL; corresponding test removed. + +Added a test for single-character CHAR columns. + +BLOB columns are now returned as Python strings instead of byte arrays. + +BINARY character columns are always returned as Python strings, and not +unicode. + +Fixed a bug introduced in 1.2.1 where the new SHOW WARNINGS support broke +SSCursor. + +Only encode the query (convert to a string) when it is a unicode instance; +re-encoding encoded strings would break things. + +Make a deep copy of conv when connecting, since it can be modified. + +Added support for new VARCHAR and BIT column types. + +DBAPISet objects were broken, but nobody noticed. + + +======================== + What's new in 1.2.1_p2 +======================== + +There are some minor build fixes which probably only affect MySQL +older than 4.0. + +If you had MySQL older than 4.1, the new charset and sql_mode +parameters didn't work right. In fact, it was impossible to create +a connection due to the charset problem. + +If you are using MySQL-4.1 or newer, there is no practical difference +between 1.2.1 and 1.2.1_p2, and you don't need to upgrade. + + +===================== + What's new in 1.2.1 +===================== + +Switched to Subversion. Was going to do this for 1.3, but a +SourceForge CVS outage has forced the issue. + +Mapped a lot of new 4.1 and 5.0 error codes to Python exceptions + +Added an API call for mysql_set_character_set(charset) (MySQL > 5.0.7) + +Added an API call for mysql_get_character_set_info() (MySQL > 5.0.10) + +Revamped the build system. Edit site.cfg if necessary (probably not +in most cases) + +Python-2.3 is now the minimum version. + +Dropped support for mx.Datetime and stringtimes; always uses Python +datetime module now. + +Improved unit tests + +New connect() options: +* charset: sets character set, implies use_unicode +* sql_mode: sets SQL mode (i.e. ANSI, etc.; see MySQL docs) + +When using MySQL-4.1 or newer, enables MULTI_STATEMENTS + +When using MySQL-5.0 or newer, enables MULTI_RESULTS + +When using MySQL-4.1 or newer, more detailed warning messages +are produced + +SET columns returned as Python Set types; you can pass a Set as +a parameter to cursor.execute(). + +Support for the new MySQL-5.0 DECIMAL implementation + +Support for Python Decimal type + +Some use of weak references internally. Cursors no longer leak +if you don't close them. Connections still do, unfortunately. + +ursor.fetchXXXDict() methods raise DeprecationWarning + +cursor.begin() is making a brief reappearence. + +cursor.callproc() now works, with some limitations. diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 9caf5bcc..00000000 --- a/INSTALL +++ /dev/null @@ -1,195 +0,0 @@ -==================== -MySQLdb Installation -==================== - -.. contents:: -.. - -Prerequisites -------------- - -+ Python 2.6, 2.7, 3.3 or higher - - * http://www.python.org/ - -+ setuptools - - * http://pypi.python.org/pypi/setuptools - -+ MySQL 5.0 or higher - - * http://www.mysql.com/downloads/ - - * MySQL-4.0 and MySQL-4.1 may work, but not supported. - - * MySQL-5.0 is supported and tested, including stored procedures. - - * MySQL-5.1 is supported (currently a release candidate) but untested. - It should work. - - * Red Hat Linux packages: - - - mysql-devel to compile - - - mysql and/or mysql-devel to run - - * MySQL.com RPM packages: - - - MySQL-devel to compile - - - MySQL-shared if you want to use their shared - library. Otherwise you'll get a statically-linked module, - which may or may not be what you want. - - - MySQL-shared to run if you compiled with MySQL-shared installed - -+ 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: - - embedded - use embedded server library (libmysqld) if True; otherwise use - one of the client libraries (default). - - threadsafe - thread-safe client library (libmysqlclient_r) if True (default); - otherwise use non-thread-safe (libmysqlclient). You should - always use the thread-safe library if you have the option; - otherwise you *may* have problems. - - static - if True, try to link against a static library; otherwise link - against dynamic libraries (default). You may need static linking - to use the embedded server. - 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. - - -RPMs -.... - -If you prefer to install RPMs, you can use the bdist_rpm command with -setup.py. This only builds the RPM; it does not install it. You may -want to use the --python=XXX option, where XXX is the name of the -Python executable, i.e. python, python2, python2.4; the default is -python. Using this will incorporate the Python executable name into -the package name for the RPM so you have install the package multiple -times if you need to support more than one version of Python. You can -also set this in setup.cfg. - - -Red Hat Linux -............. - -MySQL-python is pre-packaged in Red Hat Linux 7.x and newer. This -includes Fedora Core and Red Hat Enterprise Linux. You can also -build your own RPM packages as described above. - - -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`: http://packages.gentoo.org/search/?sstring=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 -:Revision: $Id$ diff --git a/MANIFEST.in b/MANIFEST.in index 4e03cc43..58a996de 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,13 +1,7 @@ -recursive-include doc *.rst -recursive-include tests *.py -include doc/conf.py -include MANIFEST.in -include HISTORY -include INSTALL -include README.md -include GPL-2.0 -include metadata.cfg -include site.cfg -include setup_common.py -include setup_posix.py -include setup_windows.py +recursive-include doc *.rst +recursive-include tests *.py +include doc/conf.py +include HISTORY.rst +include README.md +include LICENSE +include site.cfg diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..3f9ff8bb --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: build +build: + python setup.py build_ext -if + +.PHONY: doc +doc: + pip install . + pip install sphinx + cd doc && make html + +.PHONY: clean +clean: + find . -name '*.pyc' -delete + find . -name '__pycache__' -delete + rm -rf build + +.PHONY: check +check: + ruff *.py src ci + black *.py src ci diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py deleted file mode 100644 index fc414810..00000000 --- a/MySQLdb/__init__.py +++ /dev/null @@ -1,98 +0,0 @@ -"""MySQLdb - A DB API v2.0 compatible interface to MySQL. - -This package is a wrapper around _mysql, which mostly implements the -MySQL C API. - -connect() -- connects to server - -See the C API specification and the MySQL documentation for more info -on other items. - -For information on how MySQLdb handles type conversion, see the -MySQLdb.converters module. - -""" - -__revision__ = """$Revision$"""[11:-2] -from MySQLdb.release import __version__, version_info, __author__ - -import _mysql - -if version_info != _mysql.version_info: - raise ImportError("this is MySQLdb version %s, but _mysql is version %r" % - (version_info, _mysql.version_info)) - -threadsafety = 1 -apilevel = "2.0" -paramstyle = "format" - -from _mysql import * -from MySQLdb.constants import FIELD_TYPE -from MySQLdb.times import Date, Time, Timestamp, \ - DateFromTicks, TimeFromTicks, TimestampFromTicks - -try: - frozenset -except NameError: - from sets import ImmutableSet as frozenset - -class DBAPISet(frozenset): - - """A special type of set for which A == x is true if A is a - DBAPISet and x is a member of that set.""" - - def __eq__(self, other): - if isinstance(other, DBAPISet): - return not self.difference(other) - return other in self - - -STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, - FIELD_TYPE.VAR_STRING]) -BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, - FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB]) -NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, - FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, - FIELD_TYPE.TINY, FIELD_TYPE.YEAR]) -DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE]) -TIME = DBAPISet([FIELD_TYPE.TIME]) -TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) -DATETIME = TIMESTAMP -ROWID = DBAPISet() - -def test_DBAPISet_set_equality(): - assert STRING == STRING - -def test_DBAPISet_set_inequality(): - assert STRING != NUMBER - -def test_DBAPISet_set_equality_membership(): - assert FIELD_TYPE.VAR_STRING == STRING - -def test_DBAPISet_set_inequality_membership(): - assert FIELD_TYPE.DATE != STRING - -def Binary(x): - return bytes(x) - -def Connect(*args, **kwargs): - """Factory function for connections.Connection.""" - from MySQLdb.connections import Connection - return Connection(*args, **kwargs) - -connect = Connection = Connect - -__all__ = [ 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', - 'Date', 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', - 'TimestampFromTicks', 'DataError', 'DatabaseError', 'Error', - 'FIELD_TYPE', 'IntegrityError', 'InterfaceError', 'InternalError', - 'MySQLError', 'NULL', 'NUMBER', 'NotSupportedError', 'DBAPISet', - 'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME', - 'TIMESTAMP', 'Warning', 'apilevel', 'connect', 'connections', - 'constants', 'converters', 'cursors', 'debug', 'escape', 'escape_dict', - 'escape_sequence', 'escape_string', 'get_client_info', - 'paramstyle', 'string_literal', 'threadsafety', 'version_info'] - - - - diff --git a/MySQLdb/compat.py b/MySQLdb/compat.py deleted file mode 100644 index 70580b61..00000000 --- a/MySQLdb/compat.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys - -if sys.version_info[0] == 3: - PY2 = False - unicode = str - unichr = chr - long = int -else: - PY2 = True - unicode = unicode - unichr = unichr - long = long diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py deleted file mode 100644 index d8406db8..00000000 --- a/MySQLdb/connections.py +++ /dev/null @@ -1,372 +0,0 @@ -""" -This module implements connections for MySQLdb. Presently there is -only one class: Connection. Others are unlikely. However, you might -want to make your own subclasses. In most cases, you will probably -override Connection.default_cursor with a non-standard Cursor class. -""" -from MySQLdb import cursors -from MySQLdb.compat import unicode, PY2 -from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \ - DatabaseError, OperationalError, IntegrityError, InternalError, \ - NotSupportedError, ProgrammingError -import _mysql -import re - - -if not PY2: - # See http://bugs.python.org/issue24870 - _surrogateescape_table = [chr(i) if i < 0x80 else chr(i + 0xdc00) for i in range(256)] - - def _fast_surroundescape(s): - return s.decode('latin1').translate(_surrogateescape_table) - - -def defaulterrorhandler(connection, cursor, errorclass, errorvalue): - """ - If cursor is not None, (errorclass, errorvalue) is appended to - cursor.messages; otherwise it is appended to - connection.messages. Then errorclass is raised with errorvalue as - the value. - - You can override this with your own error handler by assigning it - to the instance. - """ - error = errorclass, errorvalue - if cursor: - cursor.messages.append(error) - else: - connection.messages.append(error) - del cursor - del connection - if isinstance(errorvalue, BaseException): - raise errorvalue - if errorclass is not None: - raise errorclass(errorvalue) - else: - raise Exception(errorvalue) - - -re_numeric_part = re.compile(r"^(\d+)") - -def numeric_part(s): - """Returns the leading numeric part of a string. - - >>> numeric_part("20-alpha") - 20 - >>> numeric_part("foo") - >>> numeric_part("16b") - 16 - """ - - m = re_numeric_part.match(s) - if m: - return int(m.group(1)) - return None - - -class Connection(_mysql.connection): - """MySQL Database Connection Object""" - - default_cursor = cursors.Cursor - waiter = None - - def __init__(self, *args, **kwargs): - """ - Create a connection to the database. It is strongly recommended - that you only use keyword parameters. Consult the MySQL C API - documentation for more information. - - :param str host: host to connect - :param str user: user to connect as - :param str password: password to use - :param str passwd: alias of password, for backward compatibility - :param str database: database to use - :param str db: alias of database, for backward compatibility - :param int port: TCP/IP port to connect to - :param str unix_socket: location of unix_socket to use - :param dict conv: conversion dictionary, see MySQLdb.converters - :param int connect_timeout: - number of seconds to wait before the connection attempt fails. - - :param bool compress: if set, compression is enabled - :param str named_pipe: if set, a named pipe is used to connect (Windows only) - :param str init_command: - command which is run once the connection is created - - :param str read_default_file: - file from which default client values are read - - :param str read_default_group: - configuration group to use from the default file - - :param type cursorclass: - class object, used to create cursors (keyword only) - - :param str use_unicode: - If True, text-like columns are returned as unicode objects - using the connection's character set. Otherwise, text-like - columns are returned as strings. columns are returned as - normal strings. Unicode objects will always be encoded to - the connection's character set regardless of this setting. - Default to False on Python 2 and True on Python 3. - - :param str charset: - If supplied, the connection character set will be changed - to this character set (MySQL-4.1 and newer). This implies - use_unicode=True. - - :param str sql_mode: - If supplied, the session SQL mode will be changed to this - setting (MySQL-4.1 and newer). For more details and legal - values, see the MySQL documentation. - - :param int client_flag: - flags to use or 0 (see MySQL docs or constants/CLIENTS.py) - - :param dict ssl: - dictionary or mapping contains SSL connection parameters; - see the MySQL documentation for more details - (mysql_ssl_set()). If this is set, and the client does not - support SSL, NotSupportedError will be raised. - - :param bool local_infile: - enables LOAD LOCAL INFILE; zero disables - - :param bool autocommit: - If False (default), autocommit is disabled. - If True, autocommit is enabled. - If None, autocommit isn't set and server default is used. - - There are a number of undocumented, non-standard methods. See the - documentation for the MySQL C API for some hints on what they do. - """ - from MySQLdb.constants import CLIENT, FIELD_TYPE - from MySQLdb.converters import conversions - from weakref import proxy - - kwargs2 = kwargs.copy() - - if 'database' in kwargs2: - kwargs2['db'] = kwargs2.pop('database') - if 'password' in kwargs2: - kwargs2['passwd'] = kwargs2.pop('password') - - if 'conv' in kwargs: - conv = kwargs['conv'] - else: - conv = conversions - - conv2 = {} - for k, v in conv.items(): - if isinstance(k, int) and isinstance(v, list): - conv2[k] = v[:] - else: - conv2[k] = v - kwargs2['conv'] = conv2 - - cursorclass = kwargs2.pop('cursorclass', self.default_cursor) - charset = kwargs2.pop('charset', '') - - if charset or not PY2: - use_unicode = True - else: - use_unicode = False - - use_unicode = kwargs2.pop('use_unicode', use_unicode) - sql_mode = kwargs2.pop('sql_mode', '') - - client_flag = kwargs.get('client_flag', 0) - client_version = tuple([ numeric_part(n) for n in _mysql.get_client_info().split('.')[:2] ]) - if client_version >= (4, 1): - client_flag |= CLIENT.MULTI_STATEMENTS - if client_version >= (5, 0): - client_flag |= CLIENT.MULTI_RESULTS - - kwargs2['client_flag'] = client_flag - - # PEP-249 requires autocommit to be initially off - autocommit = kwargs2.pop('autocommit', False) - self.waiter = kwargs2.pop('waiter', None) - - super(Connection, self).__init__(*args, **kwargs2) - self.cursorclass = cursorclass - self.encoders = dict([ (k, v) for k, v in conv.items() - if type(k) is not int ]) - - self._server_version = tuple([ numeric_part(n) for n in self.get_server_info().split('.')[:2] ]) - - db = proxy(self) - def _get_string_literal(): - # Note: string_literal() is called for bytes object on Python 3. - def string_literal(obj, dummy=None): - return db.string_literal(obj) - return string_literal - - def _get_unicode_literal(): - if PY2: - # unicode_literal is called for only unicode object. - def unicode_literal(u, dummy=None): - return db.literal(u.encode(unicode_literal.charset)) - else: - # unicode_literal() is called for arbitrary object. - def unicode_literal(u, dummy=None): - return db.literal(str(u).encode(unicode_literal.charset)) - return unicode_literal - - def _get_string_decoder(): - def string_decoder(s): - return s.decode(string_decoder.charset) - return string_decoder - - string_literal = _get_string_literal() - self.unicode_literal = unicode_literal = _get_unicode_literal() - self.string_decoder = string_decoder = _get_string_decoder() - if not charset: - charset = self.character_set_name() - self.set_character_set(charset) - - if sql_mode: - self.set_sql_mode(sql_mode) - - if use_unicode: - self.converter[FIELD_TYPE.STRING].append((None, string_decoder)) - self.converter[FIELD_TYPE.VAR_STRING].append((None, string_decoder)) - self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) - self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) - - self.encoders[bytes] = string_literal - self.encoders[unicode] = unicode_literal - self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS - if self._transactional: - if autocommit is not None: - self.autocommit(autocommit) - self.messages = [] - - def autocommit(self, on): - on = bool(on) - if self.get_autocommit() != on: - _mysql.connection.autocommit(self, on) - - def cursor(self, cursorclass=None): - """ - - Create a cursor on which queries may be performed. The - optional cursorclass parameter is used to create the - Cursor. By default, self.cursorclass=cursors.Cursor is - used. - - """ - return (cursorclass or self.cursorclass)(self) - - def query(self, query): - # Since _mysql releases GIL while querying, we need immutable buffer. - if isinstance(query, bytearray): - query = bytes(query) - if self.waiter is not None: - self.send_query(query) - self.waiter(self.fileno()) - self.read_query_result() - else: - _mysql.connection.query(self, query) - - def __enter__(self): - if self.get_autocommit(): - self.query("BEGIN") - return self.cursor() - - def __exit__(self, exc, value, tb): - if exc: - self.rollback() - else: - self.commit() - - def literal(self, o): - """If o is a single object, returns an SQL literal as a string. - If o is a non-string sequence, the items of the sequence are - converted and returned as a sequence. - - Non-standard. For internal use; do not use this in your - applications. - """ - s = self.escape(o, self.encoders) - # Python 3(~3.4) doesn't support % operation for bytes object. - # We should decode it before using %. - # Decoding with ascii and surrogateescape allows convert arbitrary - # bytes to unicode and back again. - # See http://python.org/dev/peps/pep-0383/ - if not PY2 and isinstance(s, (bytes, bytearray)): - return _fast_surroundescape(s) - return s - - def begin(self): - """Explicitly begin a connection. Non-standard. - DEPRECATED: Will be removed in 1.3. - Use an SQL BEGIN statement instead.""" - from warnings import warn - warn("begin() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - self.query("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.""" - if charset == "utf8mb4": - py_charset = "utf8" - else: - py_charset = charset - if self.character_set_name() != charset: - try: - super(Connection, self).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.string_decoder.charset = py_charset - self.unicode_literal.charset = py_charset - self.encoding = py_charset - - def set_sql_mode(self, sql_mode): - """Set the connection sql_mode. See MySQL documentation for - legal values.""" - if self._server_version < (4, 1): - raise NotSupportedError("server is too old to set sql_mode") - self.query("SET SESSION sql_mode='%s'" % sql_mode) - self.store_result() - - def show_warnings(self): - """Return detailed information about warnings as a - sequence of tuples of (Level, Code, Message). This - is only supported in MySQL-4.1 and up. If your server - is an earlier version, an empty sequence is returned.""" - if self._server_version < (4,1): return () - self.query("SHOW WARNINGS") - r = self.store_result() - warnings = r.fetch_row(0) - return warnings - - Warning = Warning - Error = Error - InterfaceError = InterfaceError - DatabaseError = DatabaseError - DataError = DataError - OperationalError = OperationalError - IntegrityError = IntegrityError - InternalError = InternalError - ProgrammingError = ProgrammingError - NotSupportedError = NotSupportedError - - errorhandler = defaulterrorhandler diff --git a/MySQLdb/constants/CR.py b/MySQLdb/constants/CR.py deleted file mode 100644 index 249dfec9..00000000 --- a/MySQLdb/constants/CR.py +++ /dev/null @@ -1,30 +0,0 @@ -"""MySQL Connection Errors - -Nearly all of these raise OperationalError. COMMANDS_OUT_OF_SYNC -raises ProgrammingError. - -""" - -MIN_ERROR = 2000 -MAX_ERROR = 2999 -UNKNOWN_ERROR = 2000 -SOCKET_CREATE_ERROR = 2001 -CONNECTION_ERROR = 2002 -CONN_HOST_ERROR = 2003 -IPSOCK_ERROR = 2004 -UNKNOWN_HOST = 2005 -SERVER_GONE_ERROR = 2006 -VERSION_ERROR = 2007 -OUT_OF_MEMORY = 2008 -WRONG_HOST_INFO = 2009 -LOCALHOST_CONNECTION = 2010 -TCP_CONNECTION = 2011 -SERVER_HANDSHAKE_ERR = 2012 -SERVER_LOST = 2013 -COMMANDS_OUT_OF_SYNC = 2014 -NAMEDPIPE_CONNECTION = 2015 -NAMEDPIPEWAIT_ERROR = 2016 -NAMEDPIPEOPEN_ERROR = 2017 -NAMEDPIPESETSTATE_ERROR = 2018 -CANT_READ_CHARSET = 2019 -NET_PACKET_TOO_LARGE = 2020 diff --git a/MySQLdb/constants/ER.py b/MySQLdb/constants/ER.py deleted file mode 100644 index ed45f3a1..00000000 --- a/MySQLdb/constants/ER.py +++ /dev/null @@ -1,467 +0,0 @@ -"""MySQL ER Constants - -These constants are error codes for the bulk of the error conditions -that may occur. - -""" - -HASHCHK = 1000 -NISAMCHK = 1001 -NO = 1002 -YES = 1003 -CANT_CREATE_FILE = 1004 -CANT_CREATE_TABLE = 1005 -CANT_CREATE_DB = 1006 -DB_CREATE_EXISTS = 1007 -DB_DROP_EXISTS = 1008 -DB_DROP_DELETE = 1009 -DB_DROP_RMDIR = 1010 -CANT_DELETE_FILE = 1011 -CANT_FIND_SYSTEM_REC = 1012 -CANT_GET_STAT = 1013 -CANT_GET_WD = 1014 -CANT_LOCK = 1015 -CANT_OPEN_FILE = 1016 -FILE_NOT_FOUND = 1017 -CANT_READ_DIR = 1018 -CANT_SET_WD = 1019 -CHECKREAD = 1020 -DISK_FULL = 1021 -DUP_KEY = 1022 -ERROR_ON_CLOSE = 1023 -ERROR_ON_READ = 1024 -ERROR_ON_RENAME = 1025 -ERROR_ON_WRITE = 1026 -FILE_USED = 1027 -FILSORT_ABORT = 1028 -FORM_NOT_FOUND = 1029 -GET_ERRNO = 1030 -ILLEGAL_HA = 1031 -KEY_NOT_FOUND = 1032 -NOT_FORM_FILE = 1033 -NOT_KEYFILE = 1034 -OLD_KEYFILE = 1035 -OPEN_AS_READONLY = 1036 -OUTOFMEMORY = 1037 -OUT_OF_SORTMEMORY = 1038 -UNEXPECTED_EOF = 1039 -CON_COUNT_ERROR = 1040 -OUT_OF_RESOURCES = 1041 -BAD_HOST_ERROR = 1042 -HANDSHAKE_ERROR = 1043 -DBACCESS_DENIED_ERROR = 1044 -ACCESS_DENIED_ERROR = 1045 -NO_DB_ERROR = 1046 -UNKNOWN_COM_ERROR = 1047 -BAD_NULL_ERROR = 1048 -BAD_DB_ERROR = 1049 -TABLE_EXISTS_ERROR = 1050 -BAD_TABLE_ERROR = 1051 -NON_UNIQ_ERROR = 1052 -SERVER_SHUTDOWN = 1053 -BAD_FIELD_ERROR = 1054 -WRONG_FIELD_WITH_GROUP = 1055 -WRONG_GROUP_FIELD = 1056 -WRONG_SUM_SELECT = 1057 -WRONG_VALUE_COUNT = 1058 -TOO_LONG_IDENT = 1059 -DUP_FIELDNAME = 1060 -DUP_KEYNAME = 1061 -DUP_ENTRY = 1062 -WRONG_FIELD_SPEC = 1063 -PARSE_ERROR = 1064 -EMPTY_QUERY = 1065 -NONUNIQ_TABLE = 1066 -INVALID_DEFAULT = 1067 -MULTIPLE_PRI_KEY = 1068 -TOO_MANY_KEYS = 1069 -TOO_MANY_KEY_PARTS = 1070 -TOO_LONG_KEY = 1071 -KEY_COLUMN_DOES_NOT_EXITS = 1072 -BLOB_USED_AS_KEY = 1073 -TOO_BIG_FIELDLENGTH = 1074 -WRONG_AUTO_KEY = 1075 -READY = 1076 -NORMAL_SHUTDOWN = 1077 -GOT_SIGNAL = 1078 -SHUTDOWN_COMPLETE = 1079 -FORCING_CLOSE = 1080 -IPSOCK_ERROR = 1081 -NO_SUCH_INDEX = 1082 -WRONG_FIELD_TERMINATORS = 1083 -BLOBS_AND_NO_TERMINATED = 1084 -TEXTFILE_NOT_READABLE = 1085 -FILE_EXISTS_ERROR = 1086 -LOAD_INFO = 1087 -ALTER_INFO = 1088 -WRONG_SUB_KEY = 1089 -CANT_REMOVE_ALL_FIELDS = 1090 -CANT_DROP_FIELD_OR_KEY = 1091 -INSERT_INFO = 1092 -INSERT_TABLE_USED = 1093 -NO_SUCH_THREAD = 1094 -KILL_DENIED_ERROR = 1095 -NO_TABLES_USED = 1096 -TOO_BIG_SET = 1097 -NO_UNIQUE_LOGFILE = 1098 -TABLE_NOT_LOCKED_FOR_WRITE = 1099 -TABLE_NOT_LOCKED = 1100 -BLOB_CANT_HAVE_DEFAULT = 1101 -WRONG_DB_NAME = 1102 -WRONG_TABLE_NAME = 1103 -TOO_BIG_SELECT = 1104 -UNKNOWN_ERROR = 1105 -UNKNOWN_PROCEDURE = 1106 -WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 -WRONG_PARAMETERS_TO_PROCEDURE = 1108 -UNKNOWN_TABLE = 1109 -FIELD_SPECIFIED_TWICE = 1110 -INVALID_GROUP_FUNC_USE = 1111 -UNSUPPORTED_EXTENSION = 1112 -TABLE_MUST_HAVE_COLUMNS = 1113 -RECORD_FILE_FULL = 1114 -UNKNOWN_CHARACTER_SET = 1115 -TOO_MANY_TABLES = 1116 -TOO_MANY_FIELDS = 1117 -TOO_BIG_ROWSIZE = 1118 -STACK_OVERRUN = 1119 -WRONG_OUTER_JOIN = 1120 -NULL_COLUMN_IN_INDEX = 1121 -CANT_FIND_UDF = 1122 -CANT_INITIALIZE_UDF = 1123 -UDF_NO_PATHS = 1124 -UDF_EXISTS = 1125 -CANT_OPEN_LIBRARY = 1126 -CANT_FIND_DL_ENTRY = 1127 -FUNCTION_NOT_DEFINED = 1128 -HOST_IS_BLOCKED = 1129 -HOST_NOT_PRIVILEGED = 1130 -PASSWORD_ANONYMOUS_USER = 1131 -PASSWORD_NOT_ALLOWED = 1132 -PASSWORD_NO_MATCH = 1133 -UPDATE_INFO = 1134 -CANT_CREATE_THREAD = 1135 -WRONG_VALUE_COUNT_ON_ROW = 1136 -CANT_REOPEN_TABLE = 1137 -INVALID_USE_OF_NULL = 1138 -REGEXP_ERROR = 1139 -MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 -NONEXISTING_GRANT = 1141 -TABLEACCESS_DENIED_ERROR = 1142 -COLUMNACCESS_DENIED_ERROR = 1143 -ILLEGAL_GRANT_FOR_TABLE = 1144 -GRANT_WRONG_HOST_OR_USER = 1145 -NO_SUCH_TABLE = 1146 -NONEXISTING_TABLE_GRANT = 1147 -NOT_ALLOWED_COMMAND = 1148 -SYNTAX_ERROR = 1149 -DELAYED_CANT_CHANGE_LOCK = 1150 -TOO_MANY_DELAYED_THREADS = 1151 -ABORTING_CONNECTION = 1152 -NET_PACKET_TOO_LARGE = 1153 -NET_READ_ERROR_FROM_PIPE = 1154 -NET_FCNTL_ERROR = 1155 -NET_PACKETS_OUT_OF_ORDER = 1156 -NET_UNCOMPRESS_ERROR = 1157 -NET_READ_ERROR = 1158 -NET_READ_INTERRUPTED = 1159 -NET_ERROR_ON_WRITE = 1160 -NET_WRITE_INTERRUPTED = 1161 -TOO_LONG_STRING = 1162 -TABLE_CANT_HANDLE_BLOB = 1163 -TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 -DELAYED_INSERT_TABLE_LOCKED = 1165 -WRONG_COLUMN_NAME = 1166 -WRONG_KEY_COLUMN = 1167 -WRONG_MRG_TABLE = 1168 -DUP_UNIQUE = 1169 -BLOB_KEY_WITHOUT_LENGTH = 1170 -PRIMARY_CANT_HAVE_NULL = 1171 -TOO_MANY_ROWS = 1172 -REQUIRES_PRIMARY_KEY = 1173 -NO_RAID_COMPILED = 1174 -UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 -KEY_DOES_NOT_EXITS = 1176 -CHECK_NO_SUCH_TABLE = 1177 -CHECK_NOT_IMPLEMENTED = 1178 -CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 -ERROR_DURING_COMMIT = 1180 -ERROR_DURING_ROLLBACK = 1181 -ERROR_DURING_FLUSH_LOGS = 1182 -ERROR_DURING_CHECKPOINT = 1183 -NEW_ABORTING_CONNECTION = 1184 -DUMP_NOT_IMPLEMENTED = 1185 -FLUSH_MASTER_BINLOG_CLOSED = 1186 -INDEX_REBUILD = 1187 -MASTER = 1188 -MASTER_NET_READ = 1189 -MASTER_NET_WRITE = 1190 -FT_MATCHING_KEY_NOT_FOUND = 1191 -LOCK_OR_ACTIVE_TRANSACTION = 1192 -UNKNOWN_SYSTEM_VARIABLE = 1193 -CRASHED_ON_USAGE = 1194 -CRASHED_ON_REPAIR = 1195 -WARNING_NOT_COMPLETE_ROLLBACK = 1196 -TRANS_CACHE_FULL = 1197 -SLAVE_MUST_STOP = 1198 -SLAVE_NOT_RUNNING = 1199 -BAD_SLAVE = 1200 -MASTER_INFO = 1201 -SLAVE_THREAD = 1202 -TOO_MANY_USER_CONNECTIONS = 1203 -SET_CONSTANTS_ONLY = 1204 -LOCK_WAIT_TIMEOUT = 1205 -LOCK_TABLE_FULL = 1206 -READ_ONLY_TRANSACTION = 1207 -DROP_DB_WITH_READ_LOCK = 1208 -CREATE_DB_WITH_READ_LOCK = 1209 -WRONG_ARGUMENTS = 1210 -NO_PERMISSION_TO_CREATE_USER = 1211 -UNION_TABLES_IN_DIFFERENT_DIR = 1212 -LOCK_DEADLOCK = 1213 -TABLE_CANT_HANDLE_FT = 1214 -CANNOT_ADD_FOREIGN = 1215 -NO_REFERENCED_ROW = 1216 -ROW_IS_REFERENCED = 1217 -CONNECT_TO_MASTER = 1218 -QUERY_ON_MASTER = 1219 -ERROR_WHEN_EXECUTING_COMMAND = 1220 -WRONG_USAGE = 1221 -WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 -CANT_UPDATE_WITH_READLOCK = 1223 -MIXING_NOT_ALLOWED = 1224 -DUP_ARGUMENT = 1225 -USER_LIMIT_REACHED = 1226 -SPECIFIC_ACCESS_DENIED_ERROR = 1227 -LOCAL_VARIABLE = 1228 -GLOBAL_VARIABLE = 1229 -NO_DEFAULT = 1230 -WRONG_VALUE_FOR_VAR = 1231 -WRONG_TYPE_FOR_VAR = 1232 -VAR_CANT_BE_READ = 1233 -CANT_USE_OPTION_HERE = 1234 -NOT_SUPPORTED_YET = 1235 -MASTER_FATAL_ERROR_READING_BINLOG = 1236 -SLAVE_IGNORED_TABLE = 1237 -INCORRECT_GLOBAL_LOCAL_VAR = 1238 -WRONG_FK_DEF = 1239 -KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 -OPERAND_COLUMNS = 1241 -SUBQUERY_NO_1_ROW = 1242 -UNKNOWN_STMT_HANDLER = 1243 -CORRUPT_HELP_DB = 1244 -CYCLIC_REFERENCE = 1245 -AUTO_CONVERT = 1246 -ILLEGAL_REFERENCE = 1247 -DERIVED_MUST_HAVE_ALIAS = 1248 -SELECT_REDUCED = 1249 -TABLENAME_NOT_ALLOWED_HERE = 1250 -NOT_SUPPORTED_AUTH_MODE = 1251 -SPATIAL_CANT_HAVE_NULL = 1252 -COLLATION_CHARSET_MISMATCH = 1253 -SLAVE_WAS_RUNNING = 1254 -SLAVE_WAS_NOT_RUNNING = 1255 -TOO_BIG_FOR_UNCOMPRESS = 1256 -ZLIB_Z_MEM_ERROR = 1257 -ZLIB_Z_BUF_ERROR = 1258 -ZLIB_Z_DATA_ERROR = 1259 -CUT_VALUE_GROUP_CONCAT = 1260 -WARN_TOO_FEW_RECORDS = 1261 -WARN_TOO_MANY_RECORDS = 1262 -WARN_NULL_TO_NOTNULL = 1263 -WARN_DATA_OUT_OF_RANGE = 1264 -WARN_DATA_TRUNCATED = 1265 -WARN_USING_OTHER_HANDLER = 1266 -CANT_AGGREGATE_2COLLATIONS = 1267 -DROP_USER = 1268 -REVOKE_GRANTS = 1269 -CANT_AGGREGATE_3COLLATIONS = 1270 -CANT_AGGREGATE_NCOLLATIONS = 1271 -VARIABLE_IS_NOT_STRUCT = 1272 -UNKNOWN_COLLATION = 1273 -SLAVE_IGNORED_SSL_PARAMS = 1274 -SERVER_IS_IN_SECURE_AUTH_MODE = 1275 -WARN_FIELD_RESOLVED = 1276 -BAD_SLAVE_UNTIL_COND = 1277 -MISSING_SKIP_SLAVE = 1278 -UNTIL_COND_IGNORED = 1279 -WRONG_NAME_FOR_INDEX = 1280 -WRONG_NAME_FOR_CATALOG = 1281 -WARN_QC_RESIZE = 1282 -BAD_FT_COLUMN = 1283 -UNKNOWN_KEY_CACHE = 1284 -WARN_HOSTNAME_WONT_WORK = 1285 -UNKNOWN_STORAGE_ENGINE = 1286 -WARN_DEPRECATED_SYNTAX = 1287 -NON_UPDATABLE_TABLE = 1288 -FEATURE_DISABLED = 1289 -OPTION_PREVENTS_STATEMENT = 1290 -DUPLICATED_VALUE_IN_TYPE = 1291 -TRUNCATED_WRONG_VALUE = 1292 -TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 -INVALID_ON_UPDATE = 1294 -UNSUPPORTED_PS = 1295 -GET_ERRMSG = 1296 -GET_TEMPORARY_ERRMSG = 1297 -UNKNOWN_TIME_ZONE = 1298 -WARN_INVALID_TIMESTAMP = 1299 -INVALID_CHARACTER_STRING = 1300 -WARN_ALLOWED_PACKET_OVERFLOWED = 1301 -CONFLICTING_DECLARATIONS = 1302 -SP_NO_RECURSIVE_CREATE = 1303 -SP_ALREADY_EXISTS = 1304 -SP_DOES_NOT_EXIST = 1305 -SP_DROP_FAILED = 1306 -SP_STORE_FAILED = 1307 -SP_LILABEL_MISMATCH = 1308 -SP_LABEL_REDEFINE = 1309 -SP_LABEL_MISMATCH = 1310 -SP_UNINIT_VAR = 1311 -SP_BADSELECT = 1312 -SP_BADRETURN = 1313 -SP_BADSTATEMENT = 1314 -UPDATE_LOG_DEPRECATED_IGNORED = 1315 -UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 -QUERY_INTERRUPTED = 1317 -SP_WRONG_NO_OF_ARGS = 1318 -SP_COND_MISMATCH = 1319 -SP_NORETURN = 1320 -SP_NORETURNEND = 1321 -SP_BAD_CURSOR_QUERY = 1322 -SP_BAD_CURSOR_SELECT = 1323 -SP_CURSOR_MISMATCH = 1324 -SP_CURSOR_ALREADY_OPEN = 1325 -SP_CURSOR_NOT_OPEN = 1326 -SP_UNDECLARED_VAR = 1327 -SP_WRONG_NO_OF_FETCH_ARGS = 1328 -SP_FETCH_NO_DATA = 1329 -SP_DUP_PARAM = 1330 -SP_DUP_VAR = 1331 -SP_DUP_COND = 1332 -SP_DUP_CURS = 1333 -SP_CANT_ALTER = 1334 -SP_SUBSELECT_NYI = 1335 -STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 -SP_VARCOND_AFTER_CURSHNDLR = 1337 -SP_CURSOR_AFTER_HANDLER = 1338 -SP_CASE_NOT_FOUND = 1339 -FPARSER_TOO_BIG_FILE = 1340 -FPARSER_BAD_HEADER = 1341 -FPARSER_EOF_IN_COMMENT = 1342 -FPARSER_ERROR_IN_PARAMETER = 1343 -FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 -VIEW_NO_EXPLAIN = 1345 -FRM_UNKNOWN_TYPE = 1346 -WRONG_OBJECT = 1347 -NONUPDATEABLE_COLUMN = 1348 -VIEW_SELECT_DERIVED = 1349 -VIEW_SELECT_CLAUSE = 1350 -VIEW_SELECT_VARIABLE = 1351 -VIEW_SELECT_TMPTABLE = 1352 -VIEW_WRONG_LIST = 1353 -WARN_VIEW_MERGE = 1354 -WARN_VIEW_WITHOUT_KEY = 1355 -VIEW_INVALID = 1356 -SP_NO_DROP_SP = 1357 -SP_GOTO_IN_HNDLR = 1358 -TRG_ALREADY_EXISTS = 1359 -TRG_DOES_NOT_EXIST = 1360 -TRG_ON_VIEW_OR_TEMP_TABLE = 1361 -TRG_CANT_CHANGE_ROW = 1362 -TRG_NO_SUCH_ROW_IN_TRG = 1363 -NO_DEFAULT_FOR_FIELD = 1364 -DIVISION_BY_ZERO = 1365 -TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 -ILLEGAL_VALUE_FOR_TYPE = 1367 -VIEW_NONUPD_CHECK = 1368 -VIEW_CHECK_FAILED = 1369 -PROCACCESS_DENIED_ERROR = 1370 -RELAY_LOG_FAIL = 1371 -PASSWD_LENGTH = 1372 -UNKNOWN_TARGET_BINLOG = 1373 -IO_ERR_LOG_INDEX_READ = 1374 -BINLOG_PURGE_PROHIBITED = 1375 -FSEEK_FAIL = 1376 -BINLOG_PURGE_FATAL_ERR = 1377 -LOG_IN_USE = 1378 -LOG_PURGE_UNKNOWN_ERR = 1379 -RELAY_LOG_INIT = 1380 -NO_BINARY_LOGGING = 1381 -RESERVED_SYNTAX = 1382 -WSAS_FAILED = 1383 -DIFF_GROUPS_PROC = 1384 -NO_GROUP_FOR_PROC = 1385 -ORDER_WITH_PROC = 1386 -LOGGING_PROHIBIT_CHANGING_OF = 1387 -NO_FILE_MAPPING = 1388 -WRONG_MAGIC = 1389 -PS_MANY_PARAM = 1390 -KEY_PART_0 = 1391 -VIEW_CHECKSUM = 1392 -VIEW_MULTIUPDATE = 1393 -VIEW_NO_INSERT_FIELD_LIST = 1394 -VIEW_DELETE_MERGE_VIEW = 1395 -CANNOT_USER = 1396 -XAER_NOTA = 1397 -XAER_INVAL = 1398 -XAER_RMFAIL = 1399 -XAER_OUTSIDE = 1400 -XAER_RMERR = 1401 -XA_RBROLLBACK = 1402 -NONEXISTING_PROC_GRANT = 1403 -PROC_AUTO_GRANT_FAIL = 1404 -PROC_AUTO_REVOKE_FAIL = 1405 -DATA_TOO_LONG = 1406 -SP_BAD_SQLSTATE = 1407 -STARTUP = 1408 -LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 -CANT_CREATE_USER_WITH_GRANT = 1410 -WRONG_VALUE_FOR_TYPE = 1411 -TABLE_DEF_CHANGED = 1412 -SP_DUP_HANDLER = 1413 -SP_NOT_VAR_ARG = 1414 -SP_NO_RETSET = 1415 -CANT_CREATE_GEOMETRY_OBJECT = 1416 -FAILED_ROUTINE_BREAK_BINLOG = 1417 -BINLOG_UNSAFE_ROUTINE = 1418 -BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 -EXEC_STMT_WITH_OPEN_CURSOR = 1420 -STMT_HAS_NO_OPEN_CURSOR = 1421 -COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 -NO_DEFAULT_FOR_VIEW_FIELD = 1423 -SP_NO_RECURSION = 1424 -TOO_BIG_SCALE = 1425 -TOO_BIG_PRECISION = 1426 -M_BIGGER_THAN_D = 1427 -WRONG_LOCK_OF_SYSTEM_TABLE = 1428 -CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 -QUERY_ON_FOREIGN_DATA_SOURCE = 1430 -FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 -FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 -FOREIGN_DATA_STRING_INVALID = 1433 -CANT_CREATE_FEDERATED_TABLE = 1434 -TRG_IN_WRONG_SCHEMA = 1435 -STACK_OVERRUN_NEED_MORE = 1436 -TOO_LONG_BODY = 1437 -WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 -TOO_BIG_DISPLAYWIDTH = 1439 -XAER_DUPID = 1440 -DATETIME_FUNCTION_OVERFLOW = 1441 -CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 -VIEW_PREVENT_UPDATE = 1443 -PS_NO_RECURSION = 1444 -SP_CANT_SET_AUTOCOMMIT = 1445 -MALFORMED_DEFINER = 1446 -VIEW_FRM_NO_USER = 1447 -VIEW_OTHER_USER = 1448 -NO_SUCH_USER = 1449 -FORBID_SCHEMA_CHANGE = 1450 -ROW_IS_REFERENCED_2 = 1451 -NO_REFERENCED_ROW_2 = 1452 -SP_BAD_VAR_SHADOW = 1453 -TRG_NO_DEFINER = 1454 -OLD_FILE_FORMAT = 1455 -SP_RECURSION_LIMIT = 1456 -SP_PROC_TABLE_CORRUPT = 1457 -ERROR_LAST = 1457 - diff --git a/MySQLdb/constants/REFRESH.py b/MySQLdb/constants/REFRESH.py deleted file mode 100644 index 4a08b94e..00000000 --- a/MySQLdb/constants/REFRESH.py +++ /dev/null @@ -1,17 +0,0 @@ -"""MySQL REFRESH Constants - -These constants seem to mostly deal with things internal to the -MySQL server. Forget you saw this. - -""" - -GRANT = 1 -LOG = 2 -TABLES = 4 -HOSTS = 8 -STATUS = 16 -THREADS = 32 -SLAVE = 64 -MASTER = 128 -READ_LOCK = 16384 -FAST = 32768 diff --git a/MySQLdb/constants/__init__.py b/MySQLdb/constants/__init__.py deleted file mode 100644 index 3da4a0e7..00000000 --- a/MySQLdb/constants/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = ['CR', 'FIELD_TYPE','CLIENT','REFRESH','ER','FLAG'] diff --git a/README.md b/README.md index 429770f8..e679b533 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,100 @@ # 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). +* PyPI: https://pypi.org/project/mysqlclient/ +* GitHub: https://github.com/PyMySQL/mysqlclient + + +## Support + +**Do Not use Github Issue Tracker to ask help. OSS Maintainer is not free tech support** + +When your question looks relating to Python rather than MySQL/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/MariaDB: + +* [MySQL Support](https://dev.mysql.com/support/) +* [Getting Help With MariaDB](https://mariadb.com/kb/en/getting-help-with-mariadb/) -This project adds Python 3 support and bug fixes. -I hope this fork is merged back to MySQLdb1 like distribute was merged back to setuptools. ## Install -### Prerequisites +### Windows + +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: -You may need to install the Python and MySQL development headers and libraries like so: +``` +$ pip install mysqlclient +``` -`sudo apt-get install python-dev libmysqlclient-dev` # Debian / Ubuntu +### macOS (Homebrew) -`sudo yum install python-devel mysql-devel` # Red Hat / CentOS +Install MySQL and mysqlclient: -On Windows, there are binary wheel you can install without MySQLConnector/C or MSVC. +```bash +$ # Assume you are activating Python 3 venv +$ brew install mysql pkg-config +$ pip install mysqlclient +``` -#### Note on Python 3 : if you are using python3 then you need to install python3-dev using the following command : +If you don't want to install MySQL server, you can use mysql-client instead: -`sudo apt-get install python3-dev` # debian / Ubuntu +```bash +$ # Assume you are activating Python 3 venv +$ brew install mysql-client pkg-config +$ export PKG_CONFIG_PATH="$(brew --prefix)/opt/mysql-client/lib/pkgconfig" +$ pip install mysqlclient +``` -`sudo yum install python3-devel ` # Red Hat / CentOS +### Linux +**Note that this is a basic step. I can not support complete step for build for all +environment. If you can see some error, you should fix it by yourself, or ask for +support in some user forum. Don't file a issue on the issue tracker.** -### Install from PyPI +You may need to install the Python 3 and MySQL development headers and libraries like so: -`pip install mysqlclient` +* `$ 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 -NOTE: Wheels for Windows may be not released with source package. You should pin version -in your `requirements.txt` to avoid trying to install newest source package. +Then you can install mysqlclient via pip now: +``` +$ pip install mysqlclient +``` -### Install from source +### Customize build (POSIX) -1. Download source by `git clone` or [zipfile](https://github.com/PyMySQL/mysqlclient-python/archive/master.zip). -2. Customize `site.cfg` -3. `python setup.py install` +mysqlclient uses `pkg-config --cflags --ldflags mysqlclient` by default for finding +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 +``` ### Documentation Documentation is hosted on [Read The Docs](https://mysqlclient.readthedocs.io/) - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..75f0c541 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +## Security contact information + +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 diff --git a/_mysql.c b/_mysql.c deleted file mode 100644 index 25af5ea9..00000000 --- a/_mysql.c +++ /dev/null @@ -1,3084 +0,0 @@ -/* -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) -any later version. Alternatively, you may use the original license -reproduced below. - -Copyright 1999 by Comstar.net, Inc., Atlanta, GA, US. - - All Rights Reserved - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Comstar.net, Inc. -or COMSTAR not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior permission. - -COMSTAR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO -EVENT SHALL COMSTAR BE LIABLE FOR ANY SPECIAL, INDIRECT OR -CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -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 "my_config.h" -#include "mysql.h" -#include "mysqld_error.h" - -#ifdef HAVE_WCSCOLL -#undef HAVE_WCSCOLL -#endif -#ifdef SIZEOF_SIZE_T -#undef SIZEOF_SIZE_T -#endif - -#include "Python.h" -#if PY_MAJOR_VERSION >= 3 -#define IS_PY3K -#define PyInt_FromLong(n) PyLong_FromLong(n) -#define PyInt_Check(n) PyLong_Check(n) -#define PyInt_AS_LONG(n) PyLong_AS_LONG(n) -#define PyString_FromString(s) PyUnicode_FromString(s) -#endif - -#include "bytesobject.h" -#include "structmember.h" -#include "errmsg.h" - -#define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) -#ifdef IS_PY3K -# define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) -#else -# define MyFree(ob) ob->ob_type->tp_free((PyObject *)ob) -#endif - -static PyObject *_mysql_MySQLError; -static PyObject *_mysql_Warning; -static PyObject *_mysql_Error; -static PyObject *_mysql_DatabaseError; -static PyObject *_mysql_InterfaceError; -static PyObject *_mysql_DataError; -static PyObject *_mysql_OperationalError; -static PyObject *_mysql_IntegrityError; -static PyObject *_mysql_InternalError; -static PyObject *_mysql_ProgrammingError; -static PyObject *_mysql_NotSupportedError; - -typedef struct { - PyObject_HEAD - MYSQL connection; - int open; - PyObject *converter; -} _mysql_ConnectionObject; - -#define check_connection(c) if (!(c->open)) return _mysql_Exception(c) -#define result_connection(r) ((_mysql_ConnectionObject *)r->conn) -#define check_result_connection(r) check_connection(result_connection(r)) - -extern PyTypeObject _mysql_ConnectionObject_Type; - -typedef struct { - PyObject_HEAD - PyObject *conn; - MYSQL_RES *result; - int nfields; - int use; - char has_next; - PyObject *converter; -} _mysql_ResultObject; - -extern PyTypeObject _mysql_ResultObject_Type; - -static int _mysql_server_init_done = 0; -#define check_server_init(x) if (!_mysql_server_init_done) { if (mysql_server_init(0, NULL, NULL)) { _mysql_Exception(NULL); return x; } else { _mysql_server_init_done = 1;} } - -#if MYSQL_VERSION_ID >= 50500 -#define HAVE_OPENSSL 1 -#endif - -/* According to https://dev.mysql.com/doc/refman/5.1/en/mysql-options.html - The MYSQL_OPT_READ_TIMEOUT apear in the version 5.1.12 */ -#if MYSQL_VERSION_ID > 50112 -#define HAVE_MYSQL_OPT_TIMEOUTS 1 -#endif - -PyObject * -_mysql_Exception(_mysql_ConnectionObject *c) -{ - PyObject *t, *e; - int merr; - - if (!(t = PyTuple_New(2))) return NULL; - if (!_mysql_server_init_done) { - e = _mysql_InternalError; - PyTuple_SET_ITEM(t, 0, PyInt_FromLong(-1L)); - PyTuple_SET_ITEM(t, 1, PyString_FromString("server not initialized")); - PyErr_SetObject(e, t); - Py_DECREF(t); - return NULL; - } - merr = mysql_errno(&(c->connection)); - if (!merr) - e = _mysql_InterfaceError; - else if (merr > CR_MAX_ERROR) { - PyTuple_SET_ITEM(t, 0, PyInt_FromLong(-1L)); - PyTuple_SET_ITEM(t, 1, PyString_FromString("error totally whack")); - PyErr_SetObject(_mysql_InterfaceError, t); - Py_DECREF(t); - return NULL; - } - else switch (merr) { - case CR_COMMANDS_OUT_OF_SYNC: - case ER_DB_CREATE_EXISTS: - case ER_SYNTAX_ERROR: - case ER_PARSE_ERROR: - case ER_NO_SUCH_TABLE: - case ER_WRONG_DB_NAME: - case ER_WRONG_TABLE_NAME: - case ER_FIELD_SPECIFIED_TWICE: - case ER_INVALID_GROUP_FUNC_USE: - case ER_UNSUPPORTED_EXTENSION: - case ER_TABLE_MUST_HAVE_COLUMNS: -#ifdef ER_CANT_DO_THIS_DURING_AN_TRANSACTION - case ER_CANT_DO_THIS_DURING_AN_TRANSACTION: -#endif - e = _mysql_ProgrammingError; - break; -#ifdef WARN_DATA_TRUNCATED - case WARN_DATA_TRUNCATED: -#ifdef WARN_NULL_TO_NOTNULL - case WARN_NULL_TO_NOTNULL: -#endif -#ifdef ER_WARN_DATA_OUT_OF_RANGE - case ER_WARN_DATA_OUT_OF_RANGE: -#endif -#ifdef ER_NO_DEFAULT - case ER_NO_DEFAULT: -#endif -#ifdef ER_PRIMARY_CANT_HAVE_NULL - case ER_PRIMARY_CANT_HAVE_NULL: -#endif -#ifdef ER_DATA_TOO_LONG - case ER_DATA_TOO_LONG: -#endif -#ifdef ER_DATETIME_FUNCTION_OVERFLOW - case ER_DATETIME_FUNCTION_OVERFLOW: -#endif - e = _mysql_DataError; - break; -#endif - case ER_DUP_ENTRY: -#ifdef ER_DUP_UNIQUE - case ER_DUP_UNIQUE: -#endif -#ifdef ER_NO_REFERENCED_ROW - case ER_NO_REFERENCED_ROW: -#endif -#ifdef ER_NO_REFERENCED_ROW_2 - case ER_NO_REFERENCED_ROW_2: -#endif -#ifdef ER_ROW_IS_REFERENCED - case ER_ROW_IS_REFERENCED: -#endif -#ifdef ER_ROW_IS_REFERENCED_2 - case ER_ROW_IS_REFERENCED_2: -#endif -#ifdef ER_CANNOT_ADD_FOREIGN - case ER_CANNOT_ADD_FOREIGN: -#endif -#ifdef ER_NO_DEFAULT_FOR_FIELD - case ER_NO_DEFAULT_FOR_FIELD: -#endif - e = _mysql_IntegrityError; - break; -#ifdef ER_WARNING_NOT_COMPLETE_ROLLBACK - case ER_WARNING_NOT_COMPLETE_ROLLBACK: -#endif -#ifdef ER_NOT_SUPPORTED_YET - case ER_NOT_SUPPORTED_YET: -#endif -#ifdef ER_FEATURE_DISABLED - case ER_FEATURE_DISABLED: -#endif -#ifdef ER_UNKNOWN_STORAGE_ENGINE - case ER_UNKNOWN_STORAGE_ENGINE: -#endif - e = _mysql_NotSupportedError; - break; - default: - if (merr < 1000) - e = _mysql_InternalError; - else - e = _mysql_OperationalError; - break; - } - PyTuple_SET_ITEM(t, 0, PyInt_FromLong((long)merr)); - PyTuple_SET_ITEM(t, 1, PyString_FromString(mysql_error(&(c->connection)))); - PyErr_SetObject(e, t); - Py_DECREF(t); - return NULL; -} - -static char _mysql_server_init__doc__[] = -"Initialize embedded server. If this client is not linked against\n\ -the embedded server library, this function does nothing.\n\ -\n\ -args -- sequence of command-line arguments\n\ -groups -- sequence of groups to use in defaults files\n\ -"; - -static PyObject *_mysql_server_init( - PyObject *self, - PyObject *args, - PyObject *kwargs) { - static char *kwlist[] = {"args", "groups", NULL}; - char **cmd_args_c=NULL, **groups_c=NULL, *s; - int cmd_argc=0, i, groupc; - PyObject *cmd_args=NULL, *groups=NULL, *ret=NULL, *item; - - if (_mysql_server_init_done) { - PyErr_SetString(_mysql_ProgrammingError, - "already initialized"); - return NULL; - } - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", kwlist, - &cmd_args, &groups)) - return NULL; - -#if MYSQL_VERSION_ID >= 40000 - if (cmd_args) { - if (!PySequence_Check(cmd_args)) { - PyErr_SetString(PyExc_TypeError, - "args must be a sequence"); - goto finish; - } - cmd_argc = PySequence_Size(cmd_args); - if (cmd_argc == -1) { - PyErr_SetString(PyExc_TypeError, - "args could not be sized"); - goto finish; - } - cmd_args_c = (char **) PyMem_Malloc(cmd_argc*sizeof(char *)); - for (i=0; i< cmd_argc; i++) { - item = PySequence_GetItem(cmd_args, i); -#ifdef IS_PY3K - s = PyUnicode_AsUTF8(item); -#else - s = PyString_AsString(item); -#endif - - Py_DECREF(item); - if (!s) { - PyErr_SetString(PyExc_TypeError, - "args must contain strings"); - goto finish; - } - cmd_args_c[i] = s; - } - } - if (groups) { - if (!PySequence_Check(groups)) { - PyErr_SetString(PyExc_TypeError, - "groups must be a sequence"); - goto finish; - } - groupc = PySequence_Size(groups); - if (groupc == -1) { - PyErr_SetString(PyExc_TypeError, - "groups could not be sized"); - goto finish; - } - groups_c = (char **) PyMem_Malloc((1+groupc)*sizeof(char *)); - for (i=0; i< groupc; i++) { - item = PySequence_GetItem(groups, i); -#ifdef IS_PY3K - s = PyUnicode_AsUTF8(item); -#else - s = PyString_AsString(item); -#endif - Py_DECREF(item); - if (!s) { - PyErr_SetString(PyExc_TypeError, - "groups must contain strings"); - goto finish; - } - groups_c[i] = s; - } - groups_c[groupc] = (char *)NULL; - } - /* even though this may block, don't give up the interpreter lock - so that the server can't be initialized multiple times. */ - if (mysql_server_init(cmd_argc, cmd_args_c, groups_c)) { - _mysql_Exception(NULL); - goto finish; - } -#endif - ret = Py_None; - Py_INCREF(Py_None); - _mysql_server_init_done = 1; - finish: - PyMem_Free(groups_c); - PyMem_Free(cmd_args_c); - return ret; -} - -static char _mysql_server_end__doc__[] = -"Shut down embedded server. If not using an embedded server, this\n\ -does nothing."; - -static PyObject *_mysql_server_end( - PyObject *self, - PyObject *args) { - if (_mysql_server_init_done) { -#if MYSQL_VERSION_ID >= 40000 - mysql_server_end(); -#endif - _mysql_server_init_done = 0; - Py_INCREF(Py_None); - return Py_None; - } - return _mysql_Exception(NULL); -} - -#if MYSQL_VERSION_ID >= 32314 -static char _mysql_thread_safe__doc__[] = -"Indicates whether the client is compiled as thread-safe."; - -static PyObject *_mysql_thread_safe( - PyObject *self, - PyObject *args) { - PyObject *flag; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_server_init(NULL); - if (!(flag=PyInt_FromLong((long)mysql_thread_safe()))) return NULL; - return flag; -} -#endif - -static char _mysql_ResultObject__doc__[] = -"result(connection, use=0, converter={}) -- Result set from a query.\n\ -\n\ -Creating instances of this class directly is an excellent way to\n\ -shoot yourself in the foot. If using _mysql.connection directly,\n\ -use connection.store_result() or connection.use_result() instead.\n\ -If using MySQLdb.Connection, this is done by the cursor class.\n\ -Just forget you ever saw this. Forget... FOR-GET..."; - -static int -_mysql_ResultObject_Initialize( - _mysql_ResultObject *self, - PyObject *args, - PyObject *kwargs) -{ - static char *kwlist[] = {"connection", "use", "converter", NULL}; - MYSQL_RES *result; - _mysql_ConnectionObject *conn=NULL; - int use=0; - PyObject *conv=NULL; - int n, i; - MYSQL_FIELD *fields; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|iO", kwlist, - &conn, &use, &conv)) - return -1; - if (!conv) { - if (!(conv = PyDict_New())) - return -1; - } - else - Py_INCREF(conv); - - self->conn = (PyObject *) conn; - Py_INCREF(conn); - self->use = use; - Py_BEGIN_ALLOW_THREADS ; - if (use) - result = mysql_use_result(&(conn->connection)); - else - result = mysql_store_result(&(conn->connection)); - self->result = result; - self->has_next = (char)mysql_more_results(&(conn->connection)); - Py_END_ALLOW_THREADS ; - if (!result) { - if (mysql_errno(&(conn->connection))) { - _mysql_Exception(conn); - return -1; - } - self->converter = PyTuple_New(0); - Py_DECREF(conv); - return 0; - } - n = mysql_num_fields(result); - self->nfields = n; - if (!(self->converter = PyTuple_New(n))) { - Py_DECREF(conv); - return -1; - } - fields = mysql_fetch_fields(result); - for (i=0; iconverter, i, fun); - } - - Py_DECREF(conv); - return 0; -} - -static int _mysql_ResultObject_traverse( - _mysql_ResultObject *self, - visitproc visit, - void *arg) -{ - int r; - if (self->converter) { - if (!(r = visit(self->converter, arg))) return r; - } - if (self->conn) - return visit(self->conn, arg); - return 0; -} - -static int _mysql_ResultObject_clear( - _mysql_ResultObject *self) -{ - Py_XDECREF(self->converter); - self->converter = NULL; - Py_XDECREF(self->conn); - self->conn = NULL; - return 0; -} - -static int -_mysql_ConnectionObject_Initialize( - _mysql_ConnectionObject *self, - PyObject *args, - PyObject *kwargs) -{ - MYSQL *conn = NULL; - PyObject *conv = NULL; - PyObject *ssl = NULL; -#if HAVE_OPENSSL - char *key = NULL, *cert = NULL, *ca = NULL, - *capath = NULL, *cipher = NULL; - PyObject *ssl_keepref[5] = {}; - int n_ssl_keepref = 0; -#endif - char *host = NULL, *user = NULL, *passwd = NULL, - *db = NULL, *unix_socket = NULL; - unsigned int port = 0; - unsigned int client_flag = 0; - static char *kwlist[] = { "host", "user", "passwd", "db", "port", - "unix_socket", "conv", - "connect_timeout", "compress", - "named_pipe", "init_command", - "read_default_file", "read_default_group", - "client_flag", "ssl", - "local_infile", -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - "read_timeout", - "write_timeout", -#endif - NULL } ; - int connect_timeout = 0; -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - int read_timeout = 0; - int write_timeout = 0; -#endif - int compress = -1, named_pipe = -1, local_infile = -1; - char *init_command=NULL, - *read_default_file=NULL, - *read_default_group=NULL; - - self->converter = NULL; - self->open = 0; - check_server_init(-1); - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - "|ssssisOiiisssiOiii:connect", -#else - "|ssssisOiiisssiOi:connect", -#endif - kwlist, - &host, &user, &passwd, &db, - &port, &unix_socket, &conv, - &connect_timeout, - &compress, &named_pipe, - &init_command, &read_default_file, - &read_default_group, - &client_flag, &ssl, - &local_infile -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - , &read_timeout - , &write_timeout -#endif - )) - return -1; - -#ifdef IS_PY3K -#define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ - if(t){d=PyUnicode_AsUTF8(t);ssl_keepref[n_ssl_keepref++]=t;}\ - PyErr_Clear();} -#else -#define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ - if(t){d=PyString_AsString(t);ssl_keepref[n_ssl_keepref++]=t;}\ - PyErr_Clear();} -#endif - - if (ssl) { -#if HAVE_OPENSSL - PyObject *value = NULL; - _stringsuck(ca, value, ssl); - _stringsuck(capath, value, ssl); - _stringsuck(cert, value, ssl); - _stringsuck(key, value, ssl); - _stringsuck(cipher, value, ssl); -#else - PyErr_SetString(_mysql_NotSupportedError, - "client library does not have SSL support"); - return -1; -#endif - } - - Py_BEGIN_ALLOW_THREADS ; - conn = mysql_init(&(self->connection)); - if (connect_timeout) { - unsigned int timeout = connect_timeout; - mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, - (char *)&timeout); - } -#ifdef HAVE_MYSQL_OPT_TIMEOUTS - if (read_timeout) { - unsigned int timeout = read_timeout; - mysql_options(&(self->connection), MYSQL_OPT_READ_TIMEOUT, - (char *)&timeout); - } - if (write_timeout) { - unsigned int timeout = write_timeout; - mysql_options(&(self->connection), MYSQL_OPT_WRITE_TIMEOUT, - (char *)&timeout); - } -#endif - if (compress != -1) { - mysql_options(&(self->connection), MYSQL_OPT_COMPRESS, 0); - client_flag |= CLIENT_COMPRESS; - } - if (named_pipe != -1) - mysql_options(&(self->connection), MYSQL_OPT_NAMED_PIPE, 0); - if (init_command != NULL) - mysql_options(&(self->connection), MYSQL_INIT_COMMAND, init_command); - if (read_default_file != NULL) - mysql_options(&(self->connection), MYSQL_READ_DEFAULT_FILE, read_default_file); - if (read_default_group != NULL) - mysql_options(&(self->connection), MYSQL_READ_DEFAULT_GROUP, read_default_group); - - if (local_infile != -1) - mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); - -#if HAVE_OPENSSL - if (ssl) { - mysql_ssl_set(&(self->connection), key, cert, ca, capath, cipher); - } -#endif - - conn = mysql_real_connect(&(self->connection), host, user, passwd, db, - port, unix_socket, client_flag); - - Py_END_ALLOW_THREADS ; - -#if HAVE_OPENSSL - if (ssl) { - int i; - for (i=0; iconverter = conv; - - /* - PyType_GenericAlloc() automatically sets up GC allocation and - tracking for GC objects, at least in 2.2.1, so it does not need to - be done here. tp_dealloc still needs to call PyObject_GC_UnTrack(), - however. - */ - self->open = 1; - return 0; -} - -static char _mysql_connect__doc__[] = -"Returns a MYSQL connection object. Exclusive use of\n\ -keyword parameters strongly recommended. Consult the\n\ -MySQL C API documentation for more details.\n\ -\n\ -host\n\ - string, host to connect\n\ -\n\ -user\n\ - string, user to connect as\n\ -\n\ -passwd\n\ - string, password to use\n\ -\n\ -db\n\ - string, database to use\n\ -\n\ -port\n\ - integer, TCP/IP port to connect to\n\ -\n\ -unix_socket\n\ - string, location of unix_socket (UNIX-ish only)\n\ -\n\ -conv\n\ - mapping, maps MySQL FIELD_TYPE.* to Python functions which\n\ - convert a string to the appropriate Python type\n\ -\n\ -connect_timeout\n\ - number of seconds to wait before the connection\n\ - attempt fails.\n\ -\n\ -compress\n\ - if set, gzip compression is enabled\n\ -\n\ -named_pipe\n\ - if set, connect to server via named pipe (Windows only)\n\ -\n\ -init_command\n\ - command which is run once the connection is created\n\ -\n\ -read_default_file\n\ - see the MySQL documentation for mysql_options()\n\ -\n\ -read_default_group\n\ - see the MySQL documentation for mysql_options()\n\ -\n\ -client_flag\n\ - client flags from MySQLdb.constants.CLIENT\n\ -\n\ -load_infile\n\ - int, non-zero enables LOAD LOCAL INFILE, zero disables\n\ -\n\ -"; - -static PyObject * -_mysql_connect( - PyObject *self, - PyObject *args, - PyObject *kwargs) -{ - _mysql_ConnectionObject *c=NULL; - - c = MyAlloc(_mysql_ConnectionObject, _mysql_ConnectionObject_Type); - if (c == NULL) return NULL; - if (_mysql_ConnectionObject_Initialize(c, args, kwargs)) { - Py_DECREF(c); - c = NULL; - } - return (PyObject *) c; -} - -static int _mysql_ConnectionObject_traverse( - _mysql_ConnectionObject *self, - visitproc visit, - void *arg) -{ - if (self->converter) - return visit(self->converter, arg); - return 0; -} - -static int _mysql_ConnectionObject_clear( - _mysql_ConnectionObject *self) -{ - Py_XDECREF(self->converter); - self->converter = NULL; - return 0; -} - -static char _mysql_ConnectionObject_fileno__doc__[] = -"Return underlaying fd for connection"; - -static PyObject * -_mysql_ConnectionObject_fileno( - _mysql_ConnectionObject *self) -{ - return PyInt_FromLong(self->connection.net.fd); -} - -static char _mysql_ConnectionObject_close__doc__[] = -"Close the connection. No further activity possible."; - -static PyObject * -_mysql_ConnectionObject_close( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (args) { - if (!PyArg_ParseTuple(args, "")) return NULL; - } - if (self->open) { - Py_BEGIN_ALLOW_THREADS - mysql_close(&(self->connection)); - Py_END_ALLOW_THREADS - self->open = 0; - } else { - PyErr_SetString(_mysql_ProgrammingError, - "closing a closed connection"); - return NULL; - } - _mysql_ConnectionObject_clear(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_affected_rows__doc__ [] = -"Return number of rows affected by the last query.\n\ -Non-standard. Use Cursor.rowcount.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_affected_rows( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - return PyLong_FromUnsignedLongLong(mysql_affected_rows(&(self->connection))); -} - -static char _mysql_debug__doc__[] = -"Does a DBUG_PUSH with the given string.\n\ -mysql_debug() uses the Fred Fish debug library.\n\ -To use this function, you must compile the client library to\n\ -support debugging.\n\ -"; -static PyObject * -_mysql_debug( - PyObject *self, - PyObject *args) -{ - char *debug; - if (!PyArg_ParseTuple(args, "s", &debug)) return NULL; - mysql_debug(debug); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_dump_debug_info__doc__[] = -"Instructs the server to write some debug information to the\n\ -log. The connected user must have the process privilege for\n\ -this to work. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_dump_debug_info( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int err; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_dump_debug_info(&(self->connection)); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_autocommit__doc__[] = -"Set the autocommit mode. True values enable; False value disable.\n\ -"; -static PyObject * -_mysql_ConnectionObject_autocommit( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int flag, err; - if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; - Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 - err = mysql_autocommit(&(self->connection), flag); -#else - { - char query[256]; - snprintf(query, 256, "SET AUTOCOMMIT=%d", flag); - err = mysql_query(&(self->connection), query); - } -#endif - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_get_autocommit__doc__[] = -"Get the autocommit mode. True when enable; False when disable.\n"; - -static PyObject * -_mysql_ConnectionObject_get_autocommit( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (self->connection.server_status & SERVER_STATUS_AUTOCOMMIT) { - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; -} - -static char _mysql_ConnectionObject_commit__doc__[] = -"Commits the current transaction\n\ -"; -static PyObject * -_mysql_ConnectionObject_commit( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int err; - if (!PyArg_ParseTuple(args, "")) return NULL; - Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 - err = mysql_commit(&(self->connection)); -#else - err = mysql_query(&(self->connection), "COMMIT"); -#endif - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_rollback__doc__[] = -"Rolls backs the current transaction\n\ -"; -static PyObject * -_mysql_ConnectionObject_rollback( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int err; - if (!PyArg_ParseTuple(args, "")) return NULL; - Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 - err = mysql_rollback(&(self->connection)); -#else - err = mysql_query(&(self->connection), "ROLLBACK"); -#endif - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_next_result__doc__[] = -"If more query results exist, next_result() reads the next query\n\ -results and returns the status back to application.\n\ -\n\ -After calling next_result() the state of the connection is as if\n\ -you had called query() for the next query. This means that you can\n\ -now call store_result(), warning_count(), affected_rows()\n\ -, and so forth. \n\ -\n\ -Returns 0 if there are more results; -1 if there are no more results\n\ -\n\ -Non-standard.\n\ -"; -static PyObject * -_mysql_ConnectionObject_next_result( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int err; - if (!PyArg_ParseTuple(args, "")) return NULL; - Py_BEGIN_ALLOW_THREADS -#if MYSQL_VERSION_ID >= 40100 - err = mysql_next_result(&(self->connection)); -#else - err = -1; -#endif - Py_END_ALLOW_THREADS - if (err > 0) return _mysql_Exception(self); - return PyInt_FromLong(err); -} - -#if MYSQL_VERSION_ID >= 40100 - -static char _mysql_ConnectionObject_set_server_option__doc__[] = -"set_server_option(option) -- Enables or disables an option\n\ -for the connection.\n\ -\n\ -Non-standard.\n\ -"; -static PyObject * -_mysql_ConnectionObject_set_server_option( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int err, flags=0; - if (!PyArg_ParseTuple(args, "i", &flags)) - return NULL; - Py_BEGIN_ALLOW_THREADS - err = mysql_set_server_option(&(self->connection), flags); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - return PyInt_FromLong(err); -} - -static char _mysql_ConnectionObject_sqlstate__doc__[] = -"Returns a string containing the SQLSTATE error code\n\ -for the last error. The error code consists of five characters.\n\ -'00000' means \"no error.\" The values are specified by ANSI SQL\n\ -and ODBC. For a list of possible values, see section 23\n\ -Error Handling in MySQL in the MySQL Manual.\n\ -\n\ -Note that not all MySQL errors are yet mapped to SQLSTATE's.\n\ -The value 'HY000' (general error) is used for unmapped errors.\n\ -\n\ -Non-standard.\n\ -"; -static PyObject * -_mysql_ConnectionObject_sqlstate( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - return PyString_FromString(mysql_sqlstate(&(self->connection))); -} - -static char _mysql_ConnectionObject_warning_count__doc__[] = -"Returns the number of warnings generated during execution\n\ -of the previous SQL statement.\n\ -\n\ -Non-standard.\n\ -"; -static PyObject * -_mysql_ConnectionObject_warning_count( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - return PyInt_FromLong(mysql_warning_count(&(self->connection))); -} - -#endif - -static char _mysql_ConnectionObject_errno__doc__[] = -"Returns the error code for the most recently invoked API function\n\ -that can succeed or fail. A return value of zero means that no error\n\ -occurred.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_errno( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - return PyInt_FromLong((long)mysql_errno(&(self->connection))); -} - -static char _mysql_ConnectionObject_error__doc__[] = -"Returns the error message for the most recently invoked API function\n\ -that can succeed or fail. An empty string ("") is returned if no error\n\ -occurred.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_error( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - return PyString_FromString(mysql_error(&(self->connection))); -} - -static char _mysql_escape_string__doc__[] = -"escape_string(s) -- quote any SQL-interpreted characters in string s.\n\ -\n\ -Use connection.escape_string(s), if you use it at all.\n\ -_mysql.escape_string(s) cannot handle character sets. You are\n\ -probably better off using connection.escape(o) instead, since\n\ -it will escape entire sequences as well as strings."; - -static PyObject * -_mysql_escape_string( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *str; - char *in, *out; - int len, size; - if (!PyArg_ParseTuple(args, "s#:escape_string", &in, &size)) return NULL; - str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); - if (!str) return PyErr_NoMemory(); - out = PyBytes_AS_STRING(str); -#if MYSQL_VERSION_ID < 32321 - len = mysql_escape_string(out, in, size); -#else - check_server_init(NULL); - - if (self && PyModule_Check((PyObject*)self)) - self = NULL; - if (self && self->open) - len = mysql_real_escape_string(&(self->connection), out, in, size); - else - len = mysql_escape_string(out, in, size); -#endif - if (_PyBytes_Resize(&str, len) < 0) return NULL; - return (str); -} - -static char _mysql_string_literal__doc__[] = -"string_literal(obj) -- converts object obj into a SQL string literal.\n\ -This means, any special SQL characters are escaped, and it is enclosed\n\ -within single quotes. In other words, it performs:\n\ -\n\ -\"'%s'\" % escape_string(str(obj))\n\ -\n\ -Use connection.string_literal(obj), if you use it at all.\n\ -_mysql.string_literal(obj) cannot handle character sets."; - -static PyObject * -_mysql_string_literal( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *str, *s, *o, *d; - char *in, *out; - int len, size; - if (self && PyModule_Check((PyObject*)self)) - self = NULL; - if (!PyArg_ParseTuple(args, "O|O:string_literal", &o, &d)) return NULL; - if (PyBytes_Check(o)) { - s = o; - Py_INCREF(s); - } else { - s = PyObject_Str(o); - if (!s) return NULL; -#ifdef IS_PY3K - { - PyObject *t = PyUnicode_AsASCIIString(s); - Py_DECREF(s); - if (!t) return NULL; - s = t; - } -#endif - } - in = PyBytes_AsString(s); - size = PyBytes_GET_SIZE(s); - str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); - if (!str) { - Py_DECREF(s); - return PyErr_NoMemory(); - } - out = PyBytes_AS_STRING(str); -#if MYSQL_VERSION_ID < 32321 - len = mysql_escape_string(out+1, in, size); -#else - check_server_init(NULL); - if (self && self->open) - len = mysql_real_escape_string(&(self->connection), out+1, in, size); - else - len = mysql_escape_string(out+1, in, size); -#endif - *out = *(out+len+1) = '\''; - if (_PyBytes_Resize(&str, len+2) < 0) return NULL; - Py_DECREF(s); - return (str); -} - -static PyObject *_mysql_NULL; - -static PyObject * -_escape_item( - PyObject *item, - PyObject *d) -{ - PyObject *quoted=NULL, *itemtype, *itemconv; - if (!(itemtype = PyObject_Type(item))) - goto error; - itemconv = PyObject_GetItem(d, itemtype); - Py_DECREF(itemtype); - if (!itemconv) { - PyErr_Clear(); - itemconv = PyObject_GetItem(d, -#ifdef IS_PY3K - (PyObject *) &PyUnicode_Type); -#else - (PyObject *) &PyString_Type); -#endif - } - if (!itemconv) { - PyErr_SetString(PyExc_TypeError, - "no default type converter defined"); - goto error; - } - Py_INCREF(d); - quoted = PyObject_CallFunction(itemconv, "OO", item, d); - Py_DECREF(d); - Py_DECREF(itemconv); -error: - return quoted; -} - -static char _mysql_escape__doc__[] = -"escape(obj, dict) -- escape any special characters in object obj\n\ -using mapping dict to provide quoting functions for each type.\n\ -Returns a SQL literal string."; -static PyObject * -_mysql_escape( - PyObject *self, - PyObject *args) -{ - PyObject *o=NULL, *d=NULL; - if (!PyArg_ParseTuple(args, "O|O:escape", &o, &d)) - return NULL; - if (d) { - if (!PyMapping_Check(d)) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } - return _escape_item(o, d); - } else { - if (!self) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } - return _escape_item(o, - ((_mysql_ConnectionObject *) self)->converter); - } -} - -static char _mysql_escape_sequence__doc__[] = -"escape_sequence(seq, dict) -- escape any special characters in sequence\n\ -seq using mapping dict to provide quoting functions for each type.\n\ -Returns a tuple of escaped items."; -static PyObject * -_mysql_escape_sequence( - PyObject *self, - PyObject *args) -{ - PyObject *o=NULL, *d=NULL, *r=NULL, *item, *quoted; - int i, n; - if (!PyArg_ParseTuple(args, "OO:escape_sequence", &o, &d)) - goto error; - if (!PyMapping_Check(d)) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } - if ((n = PyObject_Length(o)) == -1) goto error; - if (!(r = PyTuple_New(n))) goto error; - for (i=0; iresult); - fields = mysql_fetch_fields(self->result); - if (!(d = PyTuple_New(n))) return NULL; - for (i=0; iresult); - fields = mysql_fetch_fields(self->result); - if (!(d = PyTuple_New(n))) return NULL; - for (i=0; itype; - // Return bytes for binary and string types. - int binary = 0; - if (field_type == FIELD_TYPE_TINY_BLOB || - field_type == FIELD_TYPE_MEDIUM_BLOB || - field_type == FIELD_TYPE_LONG_BLOB || - field_type == FIELD_TYPE_BLOB || - field_type == FIELD_TYPE_VAR_STRING || - field_type == FIELD_TYPE_STRING || - field_type == FIELD_TYPE_GEOMETRY || - field_type == FIELD_TYPE_BIT) { - binary = 1; - } -#endif - if (rowitem) { - if (converter != Py_None) { - v = PyObject_CallFunction(converter, -#ifdef IS_PY3K - binary ? "y#" : "s#", -#else - "s#", -#endif - rowitem, - (int)length); - } else { -#ifdef IS_PY3K - if (!binary) { - v = PyUnicode_FromStringAndSize(rowitem, (int)length); - } else -#endif - v = PyBytes_FromStringAndSize(rowitem, (int)length); - } - if (!v) - return NULL; - } else { - Py_INCREF(Py_None); - v = Py_None; - } - return v; -} - -static PyObject * -_mysql_row_to_tuple( - _mysql_ResultObject *self, - MYSQL_ROW row) -{ - unsigned int n, i; - unsigned long *length; - PyObject *r, *c; - MYSQL_FIELD *fields; - - n = mysql_num_fields(self->result); - if (!(r = PyTuple_New(n))) return NULL; - length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); - for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); - if (!v) goto error; - PyTuple_SET_ITEM(r, i, v); - } - return r; - error: - Py_XDECREF(r); - return NULL; -} - -static PyObject * -_mysql_row_to_dict( - _mysql_ResultObject *self, - MYSQL_ROW row) -{ - unsigned int n, i; - unsigned long *length; - PyObject *r, *c; - MYSQL_FIELD *fields; - - n = mysql_num_fields(self->result); - if (!(r = PyDict_New())) return NULL; - length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); - for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); - if (!v) goto error; - if (!PyMapping_HasKeyString(r, fields[i].name)) { - PyMapping_SetItemString(r, fields[i].name, v); - } else { - int len; - char buf[256]; - strncpy(buf, fields[i].table, 256); - len = strlen(buf); - strncat(buf, ".", 256-len); - len = strlen(buf); - strncat(buf, fields[i].name, 256-len); - PyMapping_SetItemString(r, buf, v); - } - Py_DECREF(v); - } - return r; - error: - Py_XDECREF(r); - return NULL; -} - -static PyObject * -_mysql_row_to_dict_old( - _mysql_ResultObject *self, - MYSQL_ROW row) -{ - unsigned int n, i; - unsigned long *length; - PyObject *r, *c; - MYSQL_FIELD *fields; - - n = mysql_num_fields(self->result); - if (!(r = PyDict_New())) return NULL; - length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); - for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); - if (!v) goto error; - { - int len=0; - char buf[256]=""; - if (strlen(fields[i].table)) { - strncpy(buf, fields[i].table, 256); - len = strlen(buf); - strncat(buf, ".", 256-len); - len = strlen(buf); - } - strncat(buf, fields[i].name, 256-len); - PyMapping_SetItemString(r, buf, v); - } - Py_DECREF(v); - } - return r; - error: - Py_XDECREF(r); - return NULL; -} - -typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); - -int -_mysql__fetch_row( - _mysql_ResultObject *self, - PyObject **r, - int skiprows, - int maxrows, - _PYFUNC *convert_row) -{ - int i; - MYSQL_ROW row; - - for (i = skiprows; i<(skiprows+maxrows); i++) { - PyObject *v; - if (!self->use) - row = mysql_fetch_row(self->result); - else { - Py_BEGIN_ALLOW_THREADS; - row = mysql_fetch_row(self->result); - Py_END_ALLOW_THREADS; - } - if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { - _mysql_Exception((_mysql_ConnectionObject *)self->conn); - goto error; - } - if (!row) { - if (_PyTuple_Resize(r, i) == -1) goto error; - break; - } - v = convert_row(self, row); - if (!v) goto error; - PyTuple_SET_ITEM(*r, i, v); - } - return i-skiprows; - error: - return -1; -} - -static char _mysql_ResultObject_fetch_row__doc__[] = -"fetch_row([maxrows, how]) -- Fetches up to maxrows as a tuple.\n\ -The rows are formatted according to how:\n\ -\n\ - 0 -- tuples (default)\n\ - 1 -- dictionaries, key=column or table.column if duplicated\n\ - 2 -- dictionaries, key=table.column\n\ -"; - -static PyObject * -_mysql_ResultObject_fetch_row( - _mysql_ResultObject *self, - PyObject *args, - PyObject *kwargs) -{ - typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW); - static char *kwlist[] = { "maxrows", "how", NULL }; - static _PYFUNC *row_converters[] = - { - _mysql_row_to_tuple, - _mysql_row_to_dict, - _mysql_row_to_dict_old - }; - _PYFUNC *convert_row; - int maxrows=1, how=0, skiprows=0, rowsadded; - PyObject *r=NULL; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, - &maxrows, &how)) - return NULL; - check_result_connection(self); - if (how >= (int)sizeof(row_converters)) { - PyErr_SetString(PyExc_ValueError, "how out of range"); - return NULL; - } - convert_row = row_converters[how]; - if (maxrows) { - if (!(r = PyTuple_New(maxrows))) goto error; - rowsadded = _mysql__fetch_row(self, &r, skiprows, maxrows, - convert_row); - if (rowsadded == -1) goto error; - } else { - if (self->use) { - maxrows = 1000; - if (!(r = PyTuple_New(maxrows))) goto error; - while (1) { - rowsadded = _mysql__fetch_row(self, &r, skiprows, - maxrows, convert_row); - if (rowsadded == -1) goto error; - skiprows += rowsadded; - if (rowsadded < maxrows) break; - if (_PyTuple_Resize(&r, skiprows+maxrows) == -1) - goto error; - } - } else { - /* XXX if overflow, maxrows<0? */ - maxrows = (int) mysql_num_rows(self->result); - if (!(r = PyTuple_New(maxrows))) goto error; - rowsadded = _mysql__fetch_row(self, &r, 0, - maxrows, convert_row); - if (rowsadded == -1) goto error; - } - } - return r; - error: - Py_XDECREF(r); - return NULL; -} - -#if MYSQL_VERSION_ID >= 32303 - -static char _mysql_ConnectionObject_change_user__doc__[] = -"Changes the user and causes the database specified by db to\n\ -become the default (current) database on the connection\n\ -specified by mysql. In subsequent queries, this database is\n\ -the default for table references that do not include an\n\ -explicit database specifier.\n\ -\n\ -This function was introduced in MySQL Version 3.23.3.\n\ -\n\ -Fails unless the connected user can be authenticated or if he\n\ -doesn't have permission to use the database. In this case the\n\ -user and database are not changed.\n\ -\n\ -The db parameter may be set to None if you don't want to have\n\ -a default database.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_change_user( - _mysql_ConnectionObject *self, - PyObject *args, - PyObject *kwargs) -{ - char *user, *pwd=NULL, *db=NULL; - int r; - static char *kwlist[] = { "user", "passwd", "db", NULL } ; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", - kwlist, &user, &pwd, &db)) - return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_change_user(&(self->connection), user, pwd, db); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} -#endif - -static char _mysql_ConnectionObject_character_set_name__doc__[] = -"Returns the default character set for the current connection.\n\ -Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_character_set_name( - _mysql_ConnectionObject *self, - PyObject *args) -{ - const char *s; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); -#if MYSQL_VERSION_ID >= 32321 - s = mysql_character_set_name(&(self->connection)); -#else - s = "latin1"; -#endif - return PyString_FromString(s); -} - -#if MYSQL_VERSION_ID >= 50007 -static char _mysql_ConnectionObject_set_character_set__doc__[] = -"Sets the default character set for the current connection.\n\ -Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_set_character_set( - _mysql_ConnectionObject *self, - PyObject *args) -{ - const char *s; - int err; - if (!PyArg_ParseTuple(args, "s", &s)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - err = mysql_set_character_set(&(self->connection), s); - Py_END_ALLOW_THREADS - if (err) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} -#endif - -#if MYSQL_VERSION_ID >= 50010 -static char _mysql_ConnectionObject_get_character_set_info__doc__[] = -"Returns a dict with information about the current character set:\n\ -\n\ -collation\n\ - collation name\n\ -name\n\ - character set name\n\ -comment\n\ - comment or descriptive name\n\ -dir\n\ - character set directory\n\ -mbminlen\n\ - min. length for multibyte string\n\ -mbmaxlen\n\ - max. length for multibyte string\n\ -\n\ -Not all keys may be present, particularly dir.\n\ -\n\ -Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_get_character_set_info( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *result; - MY_CHARSET_INFO cs; - - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - mysql_get_character_set_info(&(self->connection), &cs); - if (!(result = PyDict_New())) return NULL; - if (cs.csname) - PyDict_SetItemString(result, "name", PyString_FromString(cs.csname)); - if (cs.name) - PyDict_SetItemString(result, "collation", PyString_FromString(cs.name)); - if (cs.comment) - PyDict_SetItemString(result, "comment", PyString_FromString(cs.comment)); - if (cs.dir) - PyDict_SetItemString(result, "dir", PyString_FromString(cs.dir)); - PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); - PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); - return result; -} -#endif - -static char _mysql_get_client_info__doc__[] = -"get_client_info() -- Returns a string that represents\n\ -the client library version."; -static PyObject * -_mysql_get_client_info( - PyObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_server_init(NULL); - return PyString_FromString(mysql_get_client_info()); -} - -static char _mysql_ConnectionObject_get_host_info__doc__[] = -"Returns a string that represents the MySQL client library\n\ -version. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_get_host_info( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - return PyString_FromString(mysql_get_host_info(&(self->connection))); -} - -static char _mysql_ConnectionObject_get_proto_info__doc__[] = -"Returns an unsigned integer representing the protocol version\n\ -used by the current connection. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_get_proto_info( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - return PyInt_FromLong((long)mysql_get_proto_info(&(self->connection))); -} - -static char _mysql_ConnectionObject_get_server_info__doc__[] = -"Returns a string that represents the server version number.\n\ -Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_get_server_info( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - return PyString_FromString(mysql_get_server_info(&(self->connection))); -} - -static char _mysql_ConnectionObject_info__doc__[] = -"Retrieves a string providing information about the most\n\ -recently executed query. Non-standard. Use messages or\n\ -Cursor.messages.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_info( - _mysql_ConnectionObject *self, - PyObject *args) -{ - const char *s; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - s = mysql_info(&(self->connection)); - if (s) return PyString_FromString(s); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_insert_id__doc__[] = -"Returns the ID generated for an AUTO_INCREMENT column by the previous\n\ -query. Use this function after you have performed an INSERT query into a\n\ -table that contains an AUTO_INCREMENT field.\n\ -\n\ -Note that this returns 0 if the previous query does not\n\ -generate an AUTO_INCREMENT value. If you need to save the value for\n\ -later, be sure to call this immediately after the query\n\ -that generates the value.\n\ -\n\ -The ID is updated after INSERT and UPDATE statements that generate\n\ -an AUTO_INCREMENT value or that set a column value to\n\ -LAST_INSERT_ID(expr). See section 6.3.5.2 Miscellaneous Functions\n\ -in the MySQL documentation.\n\ -\n\ -Also note that the value of the SQL LAST_INSERT_ID() function always\n\ -contains the most recently generated AUTO_INCREMENT value, and is not\n\ -reset between queries because the value of that function is maintained\n\ -in the server.\n\ -" ; - -static PyObject * -_mysql_ConnectionObject_insert_id( - _mysql_ConnectionObject *self, - PyObject *args) -{ - my_ulonglong r; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_insert_id(&(self->connection)); - Py_END_ALLOW_THREADS - return PyLong_FromUnsignedLongLong(r); -} - -static char _mysql_ConnectionObject_kill__doc__[] = -"Asks the server to kill the thread specified by pid.\n\ -Non-standard."; - -static PyObject * -_mysql_ConnectionObject_kill( - _mysql_ConnectionObject *self, - PyObject *args) -{ - unsigned long pid; - int r; - if (!PyArg_ParseTuple(args, "k:kill", &pid)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_kill(&(self->connection), pid); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_field_count__doc__[] = -"Returns the number of columns for the most recent query on the\n\ -connection. Non-standard. Will probably give you bogus results\n\ -on most cursor classes. Use Cursor.rowcount.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_field_count( - _mysql_ConnectionObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); -#if MYSQL_VERSION_ID < 32224 - return PyInt_FromLong((long)mysql_num_fields(&(self->connection))); -#else - return PyInt_FromLong((long)mysql_field_count(&(self->connection))); -#endif -} - -static char _mysql_ResultObject_num_fields__doc__[] = -"Returns the number of fields (column) in the result." ; - -static PyObject * -_mysql_ResultObject_num_fields( - _mysql_ResultObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_result_connection(self); - return PyInt_FromLong((long)mysql_num_fields(self->result)); -} - -static char _mysql_ResultObject_num_rows__doc__[] = -"Returns the number of rows in the result set. Note that if\n\ -use=1, this will not return a valid value until the entire result\n\ -set has been read.\n\ -"; - -static PyObject * -_mysql_ResultObject_num_rows( - _mysql_ResultObject *self, - PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) return NULL; - check_result_connection(self); - return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); -} - -static char _mysql_ConnectionObject_ping__doc__[] = -"Checks whether or not the connection to the server is\n\ -working. If it has gone down, an automatic reconnection is\n\ -attempted.\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\ -\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\ -\n\ -Non-standard. You should assume that ping() performs an\n\ -implicit rollback; use only when starting a new transaction.\n\ -You have been warned.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_ping( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int r, reconnect = -1; - if (!PyArg_ParseTuple(args, "|I", &reconnect)) return NULL; - check_connection(self); - if ( reconnect != -1 ) self->connection.reconnect = reconnect; - Py_BEGIN_ALLOW_THREADS - r = mysql_ping(&(self->connection)); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_query__doc__[] = -"Execute a query. store_result() or use_result() will get the\n\ -result set, if any. Non-standard. Use cursor() to create a cursor,\n\ -then cursor.execute().\n\ -" ; - -static PyObject * -_mysql_ConnectionObject_query( - _mysql_ConnectionObject *self, - PyObject *args) -{ - char *query; - int len, r; - if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self); - - Py_BEGIN_ALLOW_THREADS - r = mysql_real_query(&(self->connection), query, len); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - - -static char _mysql_ConnectionObject_send_query__doc__[] = -"Send a query. Same to query() except not wait response.\n\n\ -Use read_query_result() before calling store_result() or use_result()\n"; - -static PyObject * -_mysql_ConnectionObject_send_query( - _mysql_ConnectionObject *self, - PyObject *args) -{ - char *query; - int len, r; - MYSQL *mysql = &(self->connection); - if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; - check_connection(self); - - Py_BEGIN_ALLOW_THREADS - r = mysql_send_query(mysql, query, len); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - - -static char _mysql_ConnectionObject_read_query_result__doc__[] = -"Read result of query sent by send_query().\n"; - -static PyObject * -_mysql_ConnectionObject_read_query_result( - _mysql_ConnectionObject *self) -{ - int r; - MYSQL *mysql = &(self->connection); - check_connection(self); - - Py_BEGIN_ALLOW_THREADS - r = (int)mysql_read_query_result(mysql); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_select_db__doc__[] = -"Causes the database specified by db to become the default\n\ -(current) database on the connection specified by mysql. In subsequent\n\ -queries, this database is the default for table references that do not\n\ -include an explicit database specifier.\n\ -\n\ -Fails unless the connected user can be authenticated as having\n\ -permission to use the database.\n\ -\n\ -Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_select_db( - _mysql_ConnectionObject *self, - PyObject *args) -{ - char *db; - int r; - if (!PyArg_ParseTuple(args, "s:select_db", &db)) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_select_db(&(self->connection), db); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_shutdown__doc__[] = -"Asks the database server to shut down. The connected user must\n\ -have shutdown privileges. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_shutdown( - _mysql_ConnectionObject *self, - PyObject *args) -{ - int r; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - r = mysql_shutdown(&(self->connection) -#if MYSQL_VERSION_ID >= 40103 - , SHUTDOWN_DEFAULT -#endif - ); - Py_END_ALLOW_THREADS - if (r) return _mysql_Exception(self); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ConnectionObject_stat__doc__[] = -"Returns a character string containing information similar to\n\ -that provided by the mysqladmin status command. This includes\n\ -uptime in seconds and the number of running threads,\n\ -questions, reloads, and open tables. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_stat( - _mysql_ConnectionObject *self, - PyObject *args) -{ - const char *s; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - s = mysql_stat(&(self->connection)); - Py_END_ALLOW_THREADS - if (!s) return _mysql_Exception(self); - return PyString_FromString(s); -} - -static char _mysql_ConnectionObject_store_result__doc__[] = -"Returns a result object acquired by mysql_store_result\n\ -(results stored in the client). If no results are available,\n\ -None is returned. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_store_result( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; - _mysql_ResultObject *r=NULL; - - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - arglist = Py_BuildValue("(OiO)", self, 0, self->converter); - if (!arglist) goto error; - kwarglist = PyDict_New(); - if (!kwarglist) goto error; - r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); - if (!r) goto error; - if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) - goto error; - result = (PyObject *) r; - if (!(r->result)) { - Py_DECREF(result); - Py_INCREF(Py_None); - result = Py_None; - } - error: - Py_XDECREF(arglist); - Py_XDECREF(kwarglist); - return result; -} - -static char _mysql_ConnectionObject_thread_id__doc__[] = -"Returns the thread ID of the current connection. This value\n\ -can be used as an argument to kill() to kill the thread.\n\ -\n\ -If the connection is lost and you reconnect with ping(), the\n\ -thread ID will change. This means you should not get the\n\ -thread ID and store it for later. You should get it when you\n\ -need it.\n\ -\n\ -Non-standard."; - -static PyObject * -_mysql_ConnectionObject_thread_id( - _mysql_ConnectionObject *self, - PyObject *args) -{ - unsigned long pid; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - Py_BEGIN_ALLOW_THREADS - pid = mysql_thread_id(&(self->connection)); - Py_END_ALLOW_THREADS - return PyInt_FromLong((long)pid); -} - -static char _mysql_ConnectionObject_use_result__doc__[] = -"Returns a result object acquired by mysql_use_result\n\ -(results stored in the server). If no results are available,\n\ -None is returned. Non-standard.\n\ -"; - -static PyObject * -_mysql_ConnectionObject_use_result( - _mysql_ConnectionObject *self, - PyObject *args) -{ - PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; - _mysql_ResultObject *r=NULL; - - if (!PyArg_ParseTuple(args, "")) return NULL; - check_connection(self); - arglist = Py_BuildValue("(OiO)", self, 1, self->converter); - if (!arglist) return NULL; - kwarglist = PyDict_New(); - if (!kwarglist) goto error; - r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); - if (!r) goto error; - result = (PyObject *) r; - if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) - goto error; - if (!(r->result)) { - Py_DECREF(result); - Py_INCREF(Py_None); - result = Py_None; - } - error: - Py_DECREF(arglist); - Py_XDECREF(kwarglist); - return result; -} - -static void -_mysql_ConnectionObject_dealloc( - _mysql_ConnectionObject *self) -{ - PyObject_GC_UnTrack(self); - if (self->open) { - mysql_close(&(self->connection)); - self->open = 0; - } - MyFree(self); -} - -static PyObject * -_mysql_ConnectionObject_repr( - _mysql_ConnectionObject *self) -{ - char buf[300]; - if (self->open) - sprintf(buf, "<_mysql.connection open to '%.256s' at %lx>", - self->connection.host, - (long)self); - else - sprintf(buf, "<_mysql.connection closed at %lx>", - (long)self); - return PyString_FromString(buf); -} - -static char _mysql_ResultObject_data_seek__doc__[] = -"data_seek(n) -- seek to row n of result set"; -static PyObject * -_mysql_ResultObject_data_seek( - _mysql_ResultObject *self, - PyObject *args) -{ - unsigned int row; - if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; - check_result_connection(self); - mysql_data_seek(self->result, row); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ResultObject_row_seek__doc__[] = -"row_seek(n) -- seek by offset n rows of result set"; -static PyObject * -_mysql_ResultObject_row_seek( - _mysql_ResultObject *self, - PyObject *args) -{ - int offset; - MYSQL_ROW_OFFSET r; - if (!PyArg_ParseTuple(args, "i:row_seek", &offset)) return NULL; - check_result_connection(self); - if (self->use) { - PyErr_SetString(_mysql_ProgrammingError, - "cannot be used with connection.use_result()"); - return NULL; - } - r = mysql_row_tell(self->result); - mysql_row_seek(self->result, r+offset); - Py_INCREF(Py_None); - return Py_None; -} - -static char _mysql_ResultObject_row_tell__doc__[] = -"row_tell() -- return the current row number of the result set."; -static PyObject * -_mysql_ResultObject_row_tell( - _mysql_ResultObject *self, - PyObject *args) -{ - MYSQL_ROW_OFFSET r; - if (!PyArg_ParseTuple(args, "")) return NULL; - check_result_connection(self); - if (self->use) { - PyErr_SetString(_mysql_ProgrammingError, - "cannot be used with connection.use_result()"); - return NULL; - } - r = mysql_row_tell(self->result); - return PyInt_FromLong(r-self->result->data->data); -} - -static void -_mysql_ResultObject_dealloc( - _mysql_ResultObject *self) -{ - PyObject_GC_UnTrack((PyObject *)self); - mysql_free_result(self->result); - _mysql_ResultObject_clear(self); - MyFree(self); -} - -static PyObject * -_mysql_ResultObject_repr( - _mysql_ResultObject *self) -{ - char buf[300]; - sprintf(buf, "<_mysql.result object at %lx>", (long)self); - return PyString_FromString(buf); -} - -static PyMethodDef _mysql_ConnectionObject_methods[] = { - { - "affected_rows", - (PyCFunction)_mysql_ConnectionObject_affected_rows, - METH_VARARGS, - _mysql_ConnectionObject_affected_rows__doc__ - }, - { - "autocommit", - (PyCFunction)_mysql_ConnectionObject_autocommit, - METH_VARARGS, - _mysql_ConnectionObject_autocommit__doc__ - }, - { - "get_autocommit", - (PyCFunction)_mysql_ConnectionObject_get_autocommit, - METH_NOARGS, - _mysql_ConnectionObject_get_autocommit__doc__ - }, - { - "commit", - (PyCFunction)_mysql_ConnectionObject_commit, - METH_VARARGS, - _mysql_ConnectionObject_commit__doc__ - }, - { - "rollback", - (PyCFunction)_mysql_ConnectionObject_rollback, - METH_VARARGS, - _mysql_ConnectionObject_rollback__doc__ - }, - { - "next_result", - (PyCFunction)_mysql_ConnectionObject_next_result, - METH_VARARGS, - _mysql_ConnectionObject_next_result__doc__ - }, -#if MYSQL_VERSION_ID >= 40100 - { - "set_server_option", - (PyCFunction)_mysql_ConnectionObject_set_server_option, - METH_VARARGS, - _mysql_ConnectionObject_set_server_option__doc__ - }, - { - "sqlstate", - (PyCFunction)_mysql_ConnectionObject_sqlstate, - METH_VARARGS, - _mysql_ConnectionObject_sqlstate__doc__ - }, - { - "warning_count", - (PyCFunction)_mysql_ConnectionObject_warning_count, - METH_VARARGS, - _mysql_ConnectionObject_warning_count__doc__ - }, -#endif -#if MYSQL_VERSION_ID >= 32303 - { - "change_user", - (PyCFunction)_mysql_ConnectionObject_change_user, - METH_VARARGS | METH_KEYWORDS, - _mysql_ConnectionObject_change_user__doc__ - }, -#endif - { - "character_set_name", - (PyCFunction)_mysql_ConnectionObject_character_set_name, - METH_VARARGS, - _mysql_ConnectionObject_character_set_name__doc__ - }, -#if MYSQL_VERSION_ID >= 50007 - { - "set_character_set", - (PyCFunction)_mysql_ConnectionObject_set_character_set, - METH_VARARGS, - _mysql_ConnectionObject_set_character_set__doc__ - }, -#endif -#if MYSQL_VERSION_ID >= 50010 - { - "get_character_set_info", - (PyCFunction)_mysql_ConnectionObject_get_character_set_info, - METH_VARARGS, - _mysql_ConnectionObject_get_character_set_info__doc__ - }, -#endif - { - "close", - (PyCFunction)_mysql_ConnectionObject_close, - METH_VARARGS, - _mysql_ConnectionObject_close__doc__ - }, - { - "fileno", - (PyCFunction)_mysql_ConnectionObject_fileno, - METH_NOARGS, - _mysql_ConnectionObject_fileno__doc__ - }, - { - "dump_debug_info", - (PyCFunction)_mysql_ConnectionObject_dump_debug_info, - METH_VARARGS, - _mysql_ConnectionObject_dump_debug_info__doc__ - }, - { - "escape", - (PyCFunction)_mysql_escape, - METH_VARARGS, - _mysql_escape__doc__ - }, - { - "escape_string", - (PyCFunction)_mysql_escape_string, - METH_VARARGS, - _mysql_escape_string__doc__ - }, - { - "error", - (PyCFunction)_mysql_ConnectionObject_error, - METH_VARARGS, - _mysql_ConnectionObject_error__doc__ - }, - { - "errno", - (PyCFunction)_mysql_ConnectionObject_errno, - METH_VARARGS, - _mysql_ConnectionObject_errno__doc__ - }, - { - "field_count", - (PyCFunction)_mysql_ConnectionObject_field_count, - METH_VARARGS, - _mysql_ConnectionObject_field_count__doc__ - }, - { - "get_host_info", - (PyCFunction)_mysql_ConnectionObject_get_host_info, - METH_VARARGS, - _mysql_ConnectionObject_get_host_info__doc__ - }, - { - "get_proto_info", - (PyCFunction)_mysql_ConnectionObject_get_proto_info, - METH_VARARGS, - _mysql_ConnectionObject_get_proto_info__doc__ - }, - { - "get_server_info", - (PyCFunction)_mysql_ConnectionObject_get_server_info, - METH_VARARGS, - _mysql_ConnectionObject_get_server_info__doc__ - }, - { - "info", - (PyCFunction)_mysql_ConnectionObject_info, - METH_VARARGS, - _mysql_ConnectionObject_info__doc__ - }, - { - "insert_id", - (PyCFunction)_mysql_ConnectionObject_insert_id, - METH_VARARGS, - _mysql_ConnectionObject_insert_id__doc__ - }, - { - "kill", - (PyCFunction)_mysql_ConnectionObject_kill, - METH_VARARGS, - _mysql_ConnectionObject_kill__doc__ - }, - { - "ping", - (PyCFunction)_mysql_ConnectionObject_ping, - METH_VARARGS, - _mysql_ConnectionObject_ping__doc__ - }, - { - "query", - (PyCFunction)_mysql_ConnectionObject_query, - METH_VARARGS, - _mysql_ConnectionObject_query__doc__ - }, - { - "send_query", - (PyCFunction)_mysql_ConnectionObject_send_query, - METH_VARARGS, - _mysql_ConnectionObject_send_query__doc__, - }, - { - "read_query_result", - (PyCFunction)_mysql_ConnectionObject_read_query_result, - METH_NOARGS, - _mysql_ConnectionObject_read_query_result__doc__, - }, - { - "select_db", - (PyCFunction)_mysql_ConnectionObject_select_db, - METH_VARARGS, - _mysql_ConnectionObject_select_db__doc__ - }, - { - "shutdown", - (PyCFunction)_mysql_ConnectionObject_shutdown, - METH_VARARGS, - _mysql_ConnectionObject_shutdown__doc__ - }, - { - "stat", - (PyCFunction)_mysql_ConnectionObject_stat, - METH_VARARGS, - _mysql_ConnectionObject_stat__doc__ - }, - { - "store_result", - (PyCFunction)_mysql_ConnectionObject_store_result, - METH_VARARGS, - _mysql_ConnectionObject_store_result__doc__ - }, - { - "string_literal", - (PyCFunction)_mysql_string_literal, - METH_VARARGS, - _mysql_string_literal__doc__}, - { - "thread_id", - (PyCFunction)_mysql_ConnectionObject_thread_id, - METH_VARARGS, - _mysql_ConnectionObject_thread_id__doc__ - }, - { - "use_result", - (PyCFunction)_mysql_ConnectionObject_use_result, - METH_VARARGS, - _mysql_ConnectionObject_use_result__doc__ - }, - {NULL, NULL} /* sentinel */ -}; - -static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { - { - "open", - T_INT, - offsetof(_mysql_ConnectionObject,open), - READONLY, - "True if connection is open" - }, - { - "converter", - T_OBJECT, - offsetof(_mysql_ConnectionObject,converter), - 0, - "Type conversion mapping" - }, - { - "server_capabilities", - T_UINT, - offsetof(_mysql_ConnectionObject,connection.server_capabilities), - READONLY, - "Capabilites of server; consult MySQLdb.constants.CLIENT" - }, - { - "port", - T_UINT, - offsetof(_mysql_ConnectionObject,connection.port), - READONLY, - "TCP/IP port of the server connection" - }, - { - "client_flag", - T_UINT, - READONLY, - offsetof(_mysql_ConnectionObject,connection.client_flag), - "Client flags; refer to MySQLdb.constants.CLIENT" - }, - {NULL} /* Sentinel */ -}; - -static PyMethodDef _mysql_ResultObject_methods[] = { - { - "data_seek", - (PyCFunction)_mysql_ResultObject_data_seek, - METH_VARARGS, - _mysql_ResultObject_data_seek__doc__ - }, - { - "row_seek", - (PyCFunction)_mysql_ResultObject_row_seek, - METH_VARARGS, - _mysql_ResultObject_row_seek__doc__ - }, - { - "row_tell", - (PyCFunction)_mysql_ResultObject_row_tell, - METH_VARARGS, - _mysql_ResultObject_row_tell__doc__ - }, - { - "describe", - (PyCFunction)_mysql_ResultObject_describe, - METH_VARARGS, - _mysql_ResultObject_describe__doc__ - }, - { - "fetch_row", - (PyCFunction)_mysql_ResultObject_fetch_row, - METH_VARARGS | METH_KEYWORDS, - _mysql_ResultObject_fetch_row__doc__ - }, - { - "field_flags", - (PyCFunction)_mysql_ResultObject_field_flags, - METH_VARARGS, - _mysql_ResultObject_field_flags__doc__ - }, - { - "num_fields", - (PyCFunction)_mysql_ResultObject_num_fields, - METH_VARARGS, - _mysql_ResultObject_num_fields__doc__ - }, - { - "num_rows", - (PyCFunction)_mysql_ResultObject_num_rows, - METH_VARARGS, - _mysql_ResultObject_num_rows__doc__ - }, - {NULL, NULL} /* sentinel */ -}; - -static struct PyMemberDef _mysql_ResultObject_memberlist[] = { - { - "converter", - T_OBJECT, - offsetof(_mysql_ResultObject,converter), - READONLY, - "Type conversion mapping" - }, - { - "has_next", - T_BOOL, - offsetof(_mysql_ResultObject, has_next), - READONLY, - "Has next result" - }, - {NULL} /* Sentinel */ -}; - -static PyObject * -_mysql_ConnectionObject_getattro( - _mysql_ConnectionObject *self, - PyObject *name) -{ - char *cname; -#ifdef IS_PY3K - cname = PyUnicode_AsUTF8(name); -#else - cname = PyString_AsString(name); -#endif - if (strcmp(cname, "closed") == 0) - return PyInt_FromLong((long)!(self->open)); - - return PyObject_GenericGetAttr((PyObject *)self, name); -} - -static int -_mysql_ConnectionObject_setattro( - _mysql_ConnectionObject *self, - PyObject *name, - PyObject *v) -{ - if (v == NULL) { - PyErr_SetString(PyExc_AttributeError, - "can't delete connection attributes"); - return -1; - } - return PyObject_GenericSetAttr((PyObject *)self, name, v); -} - -static int -_mysql_ResultObject_setattro( - _mysql_ResultObject *self, - PyObject *name, - PyObject *v) -{ - if (v == NULL) { - PyErr_SetString(PyExc_AttributeError, - "can't delete connection attributes"); - return -1; - } - return PyObject_GenericSetAttr((PyObject *)self, name, v); -} - -PyTypeObject _mysql_ConnectionObject_Type = { -#ifdef IS_PY3K - PyVarObject_HEAD_INIT(NULL, 0) -#else - PyObject_HEAD_INIT(NULL) - 0, -#endif - "_mysql.connection", /* (char *)tp_name For printing */ - sizeof(_mysql_ConnectionObject), - 0, - (destructor)_mysql_ConnectionObject_dealloc, /* tp_dealloc */ - 0, /*tp_print*/ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /*tp_compare*/ - (reprfunc)_mysql_ConnectionObject_repr, /* tp_repr */ - - /* Method suites for standard classes */ - - 0, /* (PyNumberMethods *) tp_as_number */ - 0, /* (PySequenceMethods *) tp_as_sequence */ - 0, /* (PyMappingMethods *) tp_as_mapping */ - - /* More standard operations (here for binary compatibility) */ - - 0, /* (hashfunc) tp_hash */ - 0, /* (ternaryfunc) tp_call */ - 0, /* (reprfunc) tp_str */ - (getattrofunc)_mysql_ConnectionObject_getattro, /* tp_getattro */ - (setattrofunc)_mysql_ConnectionObject_setattro, /* tp_setattro */ - - /* Functions to access object as input/output buffer */ - 0, /* (PyBufferProcs *) tp_as_buffer */ - - /* (tp_flags) Flags to define presence of optional/expanded features */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, - _mysql_connect__doc__, /* (char *) tp_doc Documentation string */ - - /* call function for all accessible objects */ - (traverseproc) _mysql_ConnectionObject_traverse, /* tp_traverse */ - - /* delete references to contained objects */ - (inquiry) _mysql_ConnectionObject_clear, /* tp_clear */ - - /* rich comparisons */ - 0, /* (richcmpfunc) tp_richcompare */ - - /* weak reference enabler */ - 0, /* (long) tp_weaklistoffset */ - - /* Iterators */ - 0, /* (getiterfunc) tp_iter */ - 0, /* (iternextfunc) tp_iternext */ - - /* Attribute descriptor and subclassing stuff */ - (struct PyMethodDef *)_mysql_ConnectionObject_methods, /* tp_methods */ - (struct PyMemberDef *)_mysql_ConnectionObject_memberlist, /* tp_members */ - 0, /* (struct getsetlist *) tp_getset; */ - 0, /* (struct _typeobject *) tp_base; */ - 0, /* (PyObject *) tp_dict */ - 0, /* (descrgetfunc) tp_descr_get */ - 0, /* (descrsetfunc) tp_descr_set */ - 0, /* (long) tp_dictoffset */ - (initproc)_mysql_ConnectionObject_Initialize, /* tp_init */ - NULL, /* tp_alloc */ - NULL, /* tp_new */ - NULL, /* tp_free Low-level free-memory routine */ - 0, /* (PyObject *) tp_bases */ - 0, /* (PyObject *) tp_mro method resolution order */ - 0, /* (PyObject *) tp_defined */ -} ; - -PyTypeObject _mysql_ResultObject_Type = { -#ifdef IS_PY3K - PyVarObject_HEAD_INIT(NULL, 0) -#else - PyObject_HEAD_INIT(NULL) - 0, -#endif - "_mysql.result", - sizeof(_mysql_ResultObject), - 0, - (destructor)_mysql_ResultObject_dealloc, /* tp_dealloc */ - 0, /*tp_print*/ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /*tp_compare*/ - (reprfunc)_mysql_ResultObject_repr, /* tp_repr */ - - /* Method suites for standard classes */ - - 0, /* (PyNumberMethods *) tp_as_number */ - 0, /* (PySequenceMethods *) tp_as_sequence */ - 0, /* (PyMappingMethods *) tp_as_mapping */ - - /* More standard operations (here for binary compatibility) */ - - 0, /* (hashfunc) tp_hash */ - 0, /* (ternaryfunc) tp_call */ - 0, /* (reprfunc) tp_str */ - (getattrofunc)PyObject_GenericGetAttr, /* tp_getattro */ - (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ - - /* Functions to access object as input/output buffer */ - 0, /* (PyBufferProcs *) tp_as_buffer */ - - /* Flags to define presence of optional/expanded features */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, - - _mysql_ResultObject__doc__, /* (char *) tp_doc Documentation string */ - - /* call function for all accessible objects */ - (traverseproc) _mysql_ResultObject_traverse, /* tp_traverse */ - - /* delete references to contained objects */ - (inquiry) _mysql_ResultObject_clear, /* tp_clear */ - - /* rich comparisons */ - 0, /* (richcmpfunc) tp_richcompare */ - - /* weak reference enabler */ - 0, /* (long) tp_weaklistoffset */ - - /* Iterators */ - 0, /* (getiterfunc) tp_iter */ - 0, /* (iternextfunc) tp_iternext */ - - /* Attribute descriptor and subclassing stuff */ - (struct PyMethodDef *) _mysql_ResultObject_methods, /* tp_methods */ - (struct PyMemberDef *) _mysql_ResultObject_memberlist, /*tp_members */ - 0, /* (struct getsetlist *) tp_getset; */ - 0, /* (struct _typeobject *) tp_base; */ - 0, /* (PyObject *) tp_dict */ - 0, /* (descrgetfunc) tp_descr_get */ - 0, /* (descrsetfunc) tp_descr_set */ - 0, /* (long) tp_dictoffset */ - (initproc)_mysql_ResultObject_Initialize, /* tp_init */ - NULL, /* tp_alloc */ - NULL, /* tp_new */ - NULL, /* tp_free Low-level free-memory routine */ - 0, /* (PyObject *) tp_bases */ - 0, /* (PyObject *) tp_mro method resolution order */ - 0, /* (PyObject *) tp_defined */ -}; - -static PyMethodDef -_mysql_methods[] = { - { - "connect", - (PyCFunction)_mysql_connect, - METH_VARARGS | METH_KEYWORDS, - _mysql_connect__doc__ - }, - { - "debug", - (PyCFunction)_mysql_debug, - METH_VARARGS, - _mysql_debug__doc__ - }, - { - "escape", - (PyCFunction)_mysql_escape, - METH_VARARGS, - _mysql_escape__doc__ - }, - { - "escape_sequence", - (PyCFunction)_mysql_escape_sequence, - METH_VARARGS, - _mysql_escape_sequence__doc__ - }, - { - "escape_dict", - (PyCFunction)_mysql_escape_dict, - METH_VARARGS, - _mysql_escape_dict__doc__ - }, - { - "escape_string", - (PyCFunction)_mysql_escape_string, - METH_VARARGS, - _mysql_escape_string__doc__ - }, - { - "string_literal", - (PyCFunction)_mysql_string_literal, - METH_VARARGS, - _mysql_string_literal__doc__ - }, - { - "get_client_info", - (PyCFunction)_mysql_get_client_info, - METH_VARARGS, - _mysql_get_client_info__doc__ - }, -#if MYSQL_VERSION_ID >= 32314 - { - "thread_safe", - (PyCFunction)_mysql_thread_safe, - METH_VARARGS, - _mysql_thread_safe__doc__ - }, -#endif - { - "server_init", - (PyCFunction)_mysql_server_init, - METH_VARARGS | METH_KEYWORDS, - _mysql_server_init__doc__ - }, - { - "server_end", - (PyCFunction)_mysql_server_end, - METH_VARARGS, - _mysql_server_end__doc__ - }, - {NULL, NULL} /* sentinel */ -}; - -static PyObject * -_mysql_NewException( - PyObject *dict, - PyObject *edict, - char *name) -{ - PyObject *e; - if (!(e = PyDict_GetItemString(edict, name))) - return NULL; - if (PyDict_SetItemString(dict, name, e)) - return NULL; - Py_INCREF(e); - return e; -} - -#define QUOTE(X) _QUOTE(X) -#define _QUOTE(X) #X - -static char _mysql___doc__[] = -"an adaptation of the MySQL C API (mostly)\n\ -\n\ -You probably are better off using MySQLdb instead of using this\n\ -module directly.\n\ -\n\ -In general, renaming goes from mysql_* to _mysql.*. _mysql.connect()\n\ -returns a connection object (MYSQL). Functions which expect MYSQL * as\n\ -an argument are now methods of the connection object. A number of things\n\ -return result objects (MYSQL_RES). Functions which expect MYSQL_RES * as\n\ -an argument are now methods of the result object. Deprecated functions\n\ -(as of 3.23) are NOT implemented.\n\ -"; - -#ifdef IS_PY3K -static struct PyModuleDef _mysqlmodule = { - PyModuleDef_HEAD_INIT, - "_mysql", /* name of module */ - _mysql___doc__, /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module, - or -1 if the module keeps state in global variables. */ - _mysql_methods -}; - -PyMODINIT_FUNC -PyInit__mysql(void) -#else -DL_EXPORT(void) -init_mysql(void) -#endif -{ - PyObject *dict, *module, *emod, *edict; - -#ifndef IS_PY3K - _mysql_ConnectionObject_Type.ob_type = &PyType_Type; - _mysql_ResultObject_Type.ob_type = &PyType_Type; -#endif -#if PY_VERSION_HEX >= 0x02020000 - _mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc; - _mysql_ConnectionObject_Type.tp_new = PyType_GenericNew; - _mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc; - _mysql_ResultObject_Type.tp_new = PyType_GenericNew; -#endif -#ifdef IS_PY3K - if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) - return NULL; - if (PyType_Ready(&_mysql_ResultObject_Type) < 0) - return NULL; - - module = PyModule_Create(&_mysqlmodule); - if (!module) return module; /* this really should never happen */ -#else - _mysql_ConnectionObject_Type.tp_free = _PyObject_GC_Del; - _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; - module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, - (PyObject *)NULL, PYTHON_API_VERSION); - if (!module) return; /* this really should never happen */ -#endif - - if (!(dict = PyModule_GetDict(module))) goto error; - if (PyDict_SetItemString(dict, "version_info", - PyRun_String(QUOTE(version_info), Py_eval_input, - dict, dict))) - goto error; - if (PyDict_SetItemString(dict, "__version__", - PyString_FromString(QUOTE(__version__)))) - goto error; - if (PyDict_SetItemString(dict, "connection", - (PyObject *)&_mysql_ConnectionObject_Type)) - goto error; - Py_INCREF(&_mysql_ConnectionObject_Type); - if (PyDict_SetItemString(dict, "result", - (PyObject *)&_mysql_ResultObject_Type)) - goto error; - Py_INCREF(&_mysql_ResultObject_Type); - if (!(emod = PyImport_ImportModule("_mysql_exceptions"))) { - PyErr_Print(); - goto error; - } - if (!(edict = PyModule_GetDict(emod))) goto error; - if (!(_mysql_MySQLError = - _mysql_NewException(dict, edict, "MySQLError"))) - goto error; - if (!(_mysql_Warning = - _mysql_NewException(dict, edict, "Warning"))) - goto error; - if (!(_mysql_Error = - _mysql_NewException(dict, edict, "Error"))) - goto error; - if (!(_mysql_InterfaceError = - _mysql_NewException(dict, edict, "InterfaceError"))) - goto error; - if (!(_mysql_DatabaseError = - _mysql_NewException(dict, edict, "DatabaseError"))) - goto error; - if (!(_mysql_DataError = - _mysql_NewException(dict, edict, "DataError"))) - goto error; - if (!(_mysql_OperationalError = - _mysql_NewException(dict, edict, "OperationalError"))) - goto error; - if (!(_mysql_IntegrityError = - _mysql_NewException(dict, edict, "IntegrityError"))) - goto error; - if (!(_mysql_InternalError = - _mysql_NewException(dict, edict, "InternalError"))) - goto error; - if (!(_mysql_ProgrammingError = - _mysql_NewException(dict, edict, "ProgrammingError"))) - goto error; - if (!(_mysql_NotSupportedError = - _mysql_NewException(dict, edict, "NotSupportedError"))) - goto error; - Py_DECREF(emod); - if (!(_mysql_NULL = PyString_FromString("NULL"))) - goto error; - if (PyDict_SetItemString(dict, "NULL", _mysql_NULL)) goto error; - error: - if (PyErr_Occurred()) { - PyErr_SetString(PyExc_ImportError, - "_mysql: init failed"); - module = NULL; - } -#ifdef IS_PY3K - return module; -#endif -} -/* vim: set ts=4 sts=4 sw=4 noexpandtab : */ 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' diff --git a/ci/test_mysql.py b/ci/test_mysql.py new file mode 100644 index 00000000..498be7cf --- /dev/null +++ b/ci/test_mysql.py @@ -0,0 +1,43 @@ +# This is an example test settings file for use with the Django test suite. +# +# The 'sqlite3' backend requires only the ENGINE setting (an in- +# memory database will be used). All other backends will require a +# NAME and potentially authentication information. See the +# following section in the docs for more information: +# +# https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/ +# +# The different databases that Django supports behave differently in certain +# situations, so it is recommended to run the test suite against as many +# database backends as possible. You may want to create a separate settings +# file for each of the backends you test against. + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": "django_default", + "HOST": "127.0.0.1", + "USER": "scott", + "PASSWORD": "tiger", + "TEST": {"CHARSET": "utf8mb3", "COLLATION": "utf8mb3_general_ci"}, + }, + "other": { + "ENGINE": "django.db.backends.mysql", + "NAME": "django_other", + "HOST": "127.0.0.1", + "USER": "scott", + "PASSWORD": "tiger", + "TEST": {"CHARSET": "utf8mb3", "COLLATION": "utf8mb3_general_ci"}, + }, +} + +SECRET_KEY = "django_tests_secret_key" + +# Use a fast hasher to speed up tests. +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", +] + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +USE_TZ = False diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..014486d2 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "src/MySQLdb/constants/*" diff --git a/doc/FAQ.rst b/doc/FAQ.rst index 79e828a0..14c8f72c 100644 --- a/doc/FAQ.rst +++ b/doc/FAQ.rst @@ -9,18 +9,11 @@ Build Errors ------------ - ld: fatal: library -lmysqlclient_r: not found - -mysqlclient_r is the thread-safe library. It's not available on -all platforms, or all installations, apparently. You'll need to -reconfigure site.cfg (in MySQLdb-1.2.1 and newer) to have -threadsafe = False. - mysql.h: No such file or directory This almost always mean you don't have development packages installed. On some systems, C headers for various things (like MySQL) -are distributed as a seperate package. You'll need to figure out +are distributed as a separate package. You'll need to figure out what that is and install it, but often the name ends with -devel. Another possibility: Some older versions of mysql_config behave oddly @@ -33,7 +26,7 @@ probably the issue, but it shouldn't happen any more. ImportError ----------- - ImportError: No module named _mysql + ImportError: No module named _mysql If you see this, it's likely you did some wrong when installing MySQLdb; re-read (or read) README. _mysql is the low-level C module @@ -49,7 +42,7 @@ still have to edit a configuration file so that the setup knows where to find MySQL and what libraries to include. - ImportError: libmysqlclient_r.so.14: cannot open shared object file: No such file or directory + ImportError: libmysqlclient_r.so.14: cannot open shared object file: No such file or directory The number after .so may vary, but this means you have a version of MySQLdb compiled against one version of MySQL, and are now trying to @@ -71,10 +64,10 @@ Solutions: * reconfigure your system so that the MySQL libraries are on the default loader path. In Linux, you edit /etc/ld.so.conf and run ldconfig. For Solaris, see `Linker and Libraries Guide - `_. + `_. - ImportError: ld.so.1: python: fatal: libmtmalloc.so.1: DF_1_NOOPEN tagged object may not be dlopen()'ed + ImportError: ld.so.1: python: fatal: libmtmalloc.so.1: DF_1_NOOPEN tagged object may not be dlopen()'ed This is a weird one from Solaris. What does it mean? I have no idea. However, things like this can happen if there is some sort of a compiler @@ -86,9 +79,9 @@ different vendors. Solution: Rebuild Python or MySQL (or maybe both) from source. - ImportError: dlopen(./_mysql.so, 2): Symbol not found: _sprintf$LDBLStub - Referenced from: ./_mysql.so - Expected in: dynamic lookup + ImportError: dlopen(./_mysql.so, 2): Symbol not found: _sprintf$LDBLStub + Referenced from: ./_mysql.so + Expected in: dynamic lookup This is one from Mac OS X. It seems to have been a compiler mismatch, but this time between two different versions of GCC. It seems nearly @@ -117,7 +110,7 @@ rolled back, and they cause pending transactions to commit. Other Errors ------------ - OperationalError: (1251, 'Client does not support authentication protocol requested by server; consider upgrading MySQL client') + OperationalError: (1251, 'Client does not support authentication protocol requested by server; consider upgrading MySQL client') This means your server and client libraries are not the same version. More specifically, it probably means you have a 4.1 or newer server diff --git a/doc/MySQLdb.constants.rst b/doc/MySQLdb.constants.rst index e28dee2b..a803e3e5 100644 --- a/doc/MySQLdb.constants.rst +++ b/doc/MySQLdb.constants.rst @@ -1,59 +1,50 @@ -constants Package -================= - -:mod:`constants` Package ------------------------- - -.. automodule:: MySQLdb.constants - :members: - :undoc-members: - :show-inheritance: - -:mod:`CLIENT` Module --------------------- - -.. automodule:: MySQLdb.constants.CLIENT - :members: - :undoc-members: - :show-inheritance: - -:mod:`CR` Module ----------------- - -.. automodule:: MySQLdb.constants.CR - :members: - :undoc-members: - :show-inheritance: - -:mod:`ER` Module ----------------- - -.. automodule:: MySQLdb.constants.ER - :members: - :undoc-members: - :show-inheritance: - -:mod:`FIELD_TYPE` Module ------------------------- - -.. automodule:: MySQLdb.constants.FIELD_TYPE - :members: - :undoc-members: - :show-inheritance: - -:mod:`FLAG` Module ------------------- - -.. automodule:: MySQLdb.constants.FLAG - :members: - :undoc-members: - :show-inheritance: - -:mod:`REFRESH` Module ---------------------- - -.. automodule:: MySQLdb.constants.REFRESH - :members: - :undoc-members: - :show-inheritance: - +constants Package +================= + +:mod:`constants` Package +------------------------ + +.. automodule:: MySQLdb.constants + :members: + :undoc-members: + :show-inheritance: + +:mod:`CLIENT` Module +-------------------- + +.. automodule:: MySQLdb.constants.CLIENT + :members: + :undoc-members: + :show-inheritance: + +:mod:`CR` Module +---------------- + +.. automodule:: MySQLdb.constants.CR + :members: + :undoc-members: + :show-inheritance: + +:mod:`ER` Module +---------------- + +.. automodule:: MySQLdb.constants.ER + :members: + :undoc-members: + :show-inheritance: + +:mod:`FIELD_TYPE` Module +------------------------ + +.. automodule:: MySQLdb.constants.FIELD_TYPE + :members: + :undoc-members: + :show-inheritance: + +:mod:`FLAG` Module +------------------ + +.. automodule:: MySQLdb.constants.FLAG + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/MySQLdb.rst b/doc/MySQLdb.rst index ac690900..5e6791d5 100644 --- a/doc/MySQLdb.rst +++ b/doc/MySQLdb.rst @@ -9,37 +9,49 @@ 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:`MySQLdb._mysql` Module +---------------------------- + +.. automodule:: MySQLdb._mysql + :members: + :undoc-members: + +:mod:`MySQLdb._exceptions` Module +--------------------------------- + +.. automodule:: MySQLdb._exceptions + :members: + :undoc-members: + Subpackages ----------- diff --git a/doc/_mysql.rst b/doc/_mysql.rst deleted file mode 100644 index 4a60591b..00000000 --- a/doc/_mysql.rst +++ /dev/null @@ -1,7 +0,0 @@ -_mysql Module -============= - -.. automodule:: _mysql - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/doc/_mysql_exceptions.rst b/doc/_mysql_exceptions.rst deleted file mode 100644 index 9b65de35..00000000 --- a/doc/_mysql_exceptions.rst +++ /dev/null @@ -1,7 +0,0 @@ -_mysql_exceptions Module -======================== - -.. automodule:: _mysql_exceptions - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/conf.py b/doc/conf.py index b9f58bd7..e06003ff 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # MySQLdb documentation build configuration file, created by # sphinx-quickstart on Sun Oct 07 19:36:17 2012. @@ -11,214 +10,218 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +# skip flake8 and black for this file +# flake8: noqa +import sys +import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) +# sys.path.insert(0, os.path.abspath("..")) # -- General configuration ----------------------------------------------------- +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' +# needs_sphinx = "1.0" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] +extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = "utf-8-sig" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'MySQLdb' -copyright = u'2012, Andy Dustman' +project = "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 # built documents. # # The short X.Y version. -version = '1.2' +version = "1.2" # The full version, including alpha/beta/rc tags. -release = '1.2.4b4' +release = "1.2.4b4" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#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'] +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' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # 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 # 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, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#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' +htmlhelp_basename = "MySQLdbdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'MySQLdb.tex', u'MySQLdb Documentation', - u'Andy Dustman', 'manual'), + ("index", "MySQLdb.tex", "MySQLdb Documentation", "Andy Dustman", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # 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 -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'mysqldb', u'MySQLdb Documentation', - [u'Andy Dustman'], 1) -] +man_pages = [("index", "mysqldb", "MySQLdb Documentation", ["Andy Dustman"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -227,16 +230,22 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'MySQLdb', u'MySQLdb Documentation', - u'Andy Dustman', 'MySQLdb', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "MySQLdb", + "MySQLdb Documentation", + "Andy Dustman", + "MySQLdb", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#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' diff --git a/doc/modules.rst b/doc/modules.rst deleted file mode 100644 index 7cf3faaa..00000000 --- a/doc/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -MySQLdb -======= - -.. toctree:: - :maxdepth: 4 - - MySQLdb diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..48319f03 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +sphinx~=8.0 +sphinx-rtd-theme~=3.0.0 diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 07016710..391b162f 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -8,27 +8,27 @@ MySQLdb User's Guide Introduction ------------ -MySQLdb is an thread-compatible 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 ------------ -The ``README`` file has complete installation instructions. +The `README `_ file has complete installation instructions. -_mysql ------- +MySQLdb._mysql +-------------- If you want to write applications which are portable across databases, -use MySQLdb_, and avoid using this module directly. ``_mysql`` +use MySQLdb_, and avoid using this module directly. ``MySQLdb._mysql`` provides an interface which mostly implements the MySQL C API. For more information, see the `MySQL documentation`_. The documentation for this module is intentionally weak because you probably should use the higher-level MySQLdb module. If you really need it, use the standard MySQL docs and transliterate as necessary. -.. _`MySQL documentation`: http://dev.mysql.com/doc/ +.. _`MySQL documentation`: https://dev.mysql.com/doc/ MySQL C API translation @@ -55,48 +55,47 @@ MySQL C API function mapping C API ``_mysql`` =================================== ================================== ``mysql_affected_rows()`` ``conn.affected_rows()`` - ``mysql_autocommit()`` ``conn.autocommit()`` - ``mysql_character_set_name()`` ``conn.character_set_name()`` - ``mysql_close()`` ``conn.close()`` - ``mysql_commit()`` ``conn.commit()`` - ``mysql_connect()`` ``_mysql.connect()`` - ``mysql_data_seek()`` ``result.data_seek()`` - ``mysql_debug()`` ``_mysql.debug()`` - ``mysql_dump_debug_info`` ``conn.dump_debug_info()`` - ``mysql_escape_string()`` ``_mysql.escape_string()`` - ``mysql_fetch_row()`` ``result.fetch_row()`` + ``mysql_autocommit()`` ``conn.autocommit()`` + ``mysql_character_set_name()`` ``conn.character_set_name()`` + ``mysql_close()`` ``conn.close()`` + ``mysql_commit()`` ``conn.commit()`` + ``mysql_connect()`` ``_mysql.connect()`` + ``mysql_data_seek()`` ``result.data_seek()`` + ``mysql_debug()`` ``_mysql.debug()`` + ``mysql_dump_debug_info`` ``conn.dump_debug_info()`` + ``mysql_escape_string()`` ``_mysql.escape_string()`` + ``mysql_fetch_row()`` ``result.fetch_row()`` ``mysql_get_character_set_info()`` ``conn.get_character_set_info()`` - ``mysql_get_client_info()`` ``_mysql.get_client_info()`` - ``mysql_get_host_info()`` ``conn.get_host_info()`` - ``mysql_get_proto_info()`` ``conn.get_proto_info()`` - ``mysql_get_server_info()`` ``conn.get_server_info()`` - ``mysql_info()`` ``conn.info()`` - ``mysql_insert_id()`` ``conn.insert_id()`` - ``mysql_num_fields()`` ``result.num_fields()`` - ``mysql_num_rows()`` ``result.num_rows()`` - ``mysql_options()`` various options to ``_mysql.connect()`` - ``mysql_ping()`` ``conn.ping()`` - ``mysql_query()`` ``conn.query()`` - ``mysql_real_connect()`` ``_mysql.connect()`` - ``mysql_real_query()`` ``conn.query()`` - ``mysql_real_escape_string()`` ``conn.escape_string()`` - ``mysql_rollback()`` ``conn.rollback()`` - ``mysql_row_seek()`` ``result.row_seek()`` - ``mysql_row_tell()`` ``result.row_tell()`` - ``mysql_select_db()`` ``conn.select_db()`` - ``mysql_set_character_set()`` ``conn.set_character_set()`` + ``mysql_get_client_info()`` ``_mysql.get_client_info()`` + ``mysql_get_host_info()`` ``conn.get_host_info()`` + ``mysql_get_proto_info()`` ``conn.get_proto_info()`` + ``mysql_get_server_info()`` ``conn.get_server_info()`` + ``mysql_info()`` ``conn.info()`` + ``mysql_insert_id()`` ``conn.insert_id()`` + ``mysql_num_fields()`` ``result.num_fields()`` + ``mysql_num_rows()`` ``result.num_rows()`` + ``mysql_options()`` various options to ``_mysql.connect()`` + ``mysql_ping()`` ``conn.ping()`` + ``mysql_query()`` ``conn.query()`` + ``mysql_real_connect()`` ``_mysql.connect()`` + ``mysql_real_query()`` ``conn.query()`` + ``mysql_real_escape_string()`` ``conn.escape_string()`` + ``mysql_rollback()`` ``conn.rollback()`` + ``mysql_row_seek()`` ``result.row_seek()`` + ``mysql_row_tell()`` ``result.row_tell()`` + ``mysql_select_db()`` ``conn.select_db()`` + ``mysql_set_character_set()`` ``conn.set_character_set()`` ``mysql_ssl_set()`` ``ssl`` option to ``_mysql.connect()`` - ``mysql_stat()`` ``conn.stat()`` - ``mysql_store_result()`` ``conn.store_result()`` - ``mysql_thread_id()`` ``conn.thread_id()`` - ``mysql_thread_safe_client()`` ``conn.thread_safe_client()`` - ``mysql_use_result()`` ``conn.use_result()`` - ``mysql_warning_count()`` ``conn.warning_count()`` - ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` - ``CR_*`` ``MySQLdb.constants.CR.*`` - ``ER_*`` ``MySQLdb.constants.ER.*`` - ``FIELD_TYPE_*`` ``MySQLdb.constants.FIELD_TYPE.*`` - ``FLAG_*`` ``MySQLdb.constants.FLAG.*`` + ``mysql_stat()`` ``conn.stat()`` + ``mysql_store_result()`` ``conn.store_result()`` + ``mysql_thread_id()`` ``conn.thread_id()`` + ``mysql_use_result()`` ``conn.use_result()`` + ``mysql_warning_count()`` ``conn.warning_count()`` + ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` + ``CR_*`` ``MySQLdb.constants.CR.*`` + ``ER_*`` ``MySQLdb.constants.ER.*`` + ``FIELD_TYPE_*`` ``MySQLdb.constants.FIELD_TYPE.*`` + ``FLAG_*`` ``MySQLdb.constants.FLAG.*`` =================================== ================================== @@ -107,7 +106,7 @@ Okay, so you want to use ``_mysql`` anyway. Here are some examples. The simplest possible database connection is:: - import _mysql + from MySQLdb import _mysql db=_mysql.connect() This creates a connection to the MySQL server running on the local @@ -126,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. @@ -146,13 +145,13 @@ 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 about `option files`_ for more details. -.. _`option files`: http://dev.mysql.com/doc/mysql/en/Option_files.html +.. _`option files`: http://dev.mysql.com/doc/refman/en/option-files.html So now you have an open connection as ``db`` and want to do a query. Well, there are no cursors in MySQL, and no parameter @@ -163,8 +162,8 @@ substitution, so you have to pass a complete query string to WHERE price < 5""") There's no return value from this, but exceptions can be raised. The -exceptions are defined in a separate module, ``_mysql_exceptions``, -but ``_mysql`` exports them. Read DB API specification PEP-249_ to +exceptions are defined in a separate module, ``MySQLdb._exceptions``, +but ``MySQLdb._mysql`` exports them. Read DB API specification PEP-249_ to find out what they are, or you can use the catch-all ``MySQLError``. .. _PEP-249: https://www.python.org/dev/peps/pep-0249/ @@ -214,7 +213,7 @@ implicitly asked for one row, since we didn't specify ``maxrows``. The other oddity is: Assuming these are numeric columns, why are they returned as strings? Because MySQL returns all data as strings and expects you to convert it yourself. This would be a real pain in the -ass, but in fact, ``_mysql`` can do this for you. (And ``MySQLdb`` +ass, but in fact, ``MySQLdb._mysql`` can do this for you. (And ``MySQLdb`` does do this for you.) To have automatic type conversion done, you need to create a type converter dictionary, and pass this to ``connect()`` as the ``conv`` keyword parameter. @@ -260,35 +259,35 @@ Functions and attributes Only a few top-level functions and attributes are defined within MySQLdb. -connect(parameters...) - Constructor for creating a connection to the - database. Returns a Connection Object. Parameters are the - same as for the MySQL C API. In addition, there are a few - additional keywords that correspond to what you would pass - ``mysql_options()`` before connecting. Note that some - parameters must be specified as keyword arguments! The - default value for each parameter is NULL or zero, as - appropriate. Consult the MySQL documentation for more - details. The important parameters are: +connect(parameters...) + Constructor for creating a connection to the + database. Returns a Connection Object. Parameters are the + same as for the MySQL C API. In addition, there are a few + additional keywords that correspond to what you would pass + ``mysql_options()`` before connecting. Note that some + parameters must be specified as keyword arguments! The + default value for each parameter is NULL or zero, as + appropriate. Consult the MySQL documentation for more + details. The important parameters are: host - name of host to connect to. Default: use the local host + name of host to connect to. Default: use the local host via a UNIX socket (where applicable) user 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 - TCP port of MySQL server. Default: standard port (3306). + port + TCP port of MySQL server. Default: standard port (3306). unix_socket - location of UNIX socket. Default: use default location or + location of UNIX socket. Default: use default location or TCP for remote hosts. conv @@ -331,45 +330,88 @@ connect(parameters...) connecting (MySQL-4.1 and later), you'll need to put the correct character set name in connection.charset. - If False, text-like columns are returned as normal strings, - but you can always write Unicode strings. + If False, text-like columns are returned as normal strings, + but you can always write Unicode strings. - *This must be a keyword parameter.* + *This must be a keyword parameter.* - charset - If present, the connection character set will be changed - to this character set, if they are not equal. Support for - changing the character set requires MySQL-4.1 and later - server; if the server is too old, UnsupportedError will be - raised. This option implies use_unicode=True, but you can - override this with use_unicode=False, though you probably - shouldn't. + charset + If present, the connection character set will be changed + to this character set, if they are not equal. Support for + changing the character set requires MySQL-4.1 and later + server; if the server is too old, UnsupportedError will be + raised. This option implies use_unicode=True, but you can + override this with use_unicode=False, though you probably + shouldn't. - If not present, the default character set is used. + If not present, the default character set is used. - *This must be a keyword parameter.* + *This must be a keyword parameter.* - sql_mode - If present, the session SQL mode will be set to the given - string. For more information on sql_mode, see the MySQL - documentation. Only available for 4.1 and newer servers. + collation + If ``charset`` and ``collation`` are both supplied, the + character set and collation for the current connection + will be set. - If not present, the session SQL mode will be unchanged. + If omitted, empty string, or None, the default collation + for the ``charset`` is implied by the database server. - *This must be a keyword parameter.* + To learn more about the quiddities of character sets and + collations, consult the `MySQL docs + `_ + and `MariaDB docs + `_ - ssl - This parameter takes a dictionary or mapping, where the - keys are parameter names used by the mysql_ssl_set_ MySQL - C API call. If this is set, it initiates an SSL connection - to the server; if there is no SSL support in the client, - an exception is raised. *This must be a keyword - parameter.* + *This must be a keyword parameter.* -.. _mysql_ssl_set: http://dev.mysql.com/doc/mysql/en/mysql_ssl_set.html + sql_mode + If present, the session SQL mode will be set to the given + string. For more information on sql_mode, see the MySQL + documentation. Only available for 4.1 and newer servers. + If not present, the session SQL mode will be unchanged. -apilevel + *This must be a keyword parameter.* + + ssl_mode + If present, specify the security settings for the + connection to the server. For more information on ssl_mode, + see the MySQL documentation. Only one of 'DISABLED', + 'PREFERRED', 'REQUIRED', 'VERIFY_CA', 'VERIFY_IDENTITY' + can be specified. + + If not present, the session ssl_mode will be unchanged, + but in version 5.7 and later, the default is PREFERRED. + + *This must be a keyword parameter.* + + ssl + This parameter takes a dictionary or mapping, where the + keys are parameter names used by the mysql_ssl_set_ MySQL + C API call. If this is set, it initiates an SSL connection + to the server; if there is no SSL support in the client, + an exception is raised. *This must be a keyword + parameter.* + + 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 + + +apilevel String constant stating the supported DB API level. '2.0' threadsafety @@ -385,7 +427,7 @@ threadsafety SSCursor (which uses ``mysql_use_result()``; with the latter you must ensure all the rows have been read before another query can be executed. It is further complicated by the addition of - transactions, since transactions start when a cursor execute a + transactions, since transactions start when a cursor executes a query, but end when ``COMMIT`` or ``ROLLBACK`` is executed by the Connection object. Two threads simply cannot share a connection while a transaction is in progress, in addition to @@ -434,14 +476,14 @@ conv can be either: * a callable object which takes a string argument (the MySQL - value),' returning a Python value + value), returning a Python value * a sequence of 2-tuples, where the first value is a combination - of flags from ``MySQLdb.constants.FLAG``, and the second value - is a function as above. The sequence is tested until the flags - on the field match those of the first value. If both values - are None, then the default conversion is done. Presently this - is only used to distinquish TEXT and BLOB columns. + of flags from ``MySQLdb.constants.FLAG``, and the second value + is a function as above. The sequence is tested until the flags + on the field match those of the first value. If both values + are None, then the default conversion is done. Presently this + is only used to distinguish TEXT and BLOB columns. If the key is a Python type or class, then the value is a callable Python object (usually a function) taking two arguments @@ -500,7 +542,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 @@ -522,7 +564,7 @@ close() info() Returns some information about the last query. Normally - you don't need to check this. If there are any MySQL + you don't need to check this. If there are any MySQL warnings, it will cause a Warning to be issued through the Python warning module. By default, Warning causes a message to appear on the console. However, it is possible @@ -542,15 +584,15 @@ nextset() sets, it returns None; otherwise it returns a true value. Note that MySQL doesn't support multiple result sets until 4.1. - + Some examples ............. -The ``connect()`` method works nearly the same as with `_mysql`_:: +The ``connect()`` method works nearly the same as with `MySQLDB._mysql`_:: import MySQLdb - db=MySQLdb.connect(passwd="moonpie",db="thangs") + db=MySQLdb.connect(password="moonpie",database="thangs") To perform a query, you first need a cursor, and then you can execute queries on it:: @@ -598,13 +640,13 @@ The only other method you are very likely to use is when you have to do a multi-row insert:: c.executemany( - """INSERT INTO breakfast (name, spam, eggs, sausage, price) - VALUES (%s, %s, %s, %s, %s)""", - [ - ("Spam and Sausage Lover's Plate", 5, 1, 8, 7.95 ), - ("Not So Much Spam Plate", 3, 2, 0, 3.95 ), - ("Don't Wany ANY SPAM! Plate", 0, 4, 3, 5.95 ) - ] ) + """INSERT INTO breakfast (name, spam, eggs, sausage, price) + VALUES (%s, %s, %s, %s, %s)""", + [ + ("Spam and Sausage Lover's Plate", 5, 1, 8, 7.95 ), + ("Not So Much Spam Plate", 3, 2, 0, 3.95 ), + ("Don't Wany ANY SPAM! Plate", 0, 4, 3, 5.95 ) + ] ) Here we are inserting three rows of five values. Notice that there is a mix of types (strings, ints, floats) though we still only use @@ -615,7 +657,7 @@ Using and extending ------------------- In general, it is probably wise to not directly interact with the DB -API except for small applicatons. Databases, even SQL databases, vary +API except for small applications. Databases, even SQL databases, vary widely in capabilities and may have non-standard features. The DB API does a good job of providing a reasonably portable interface but some methods are non-portable. Specifically, the parameters accepted by @@ -653,7 +695,6 @@ CursorTupleRowsMixIn Causes the cursor to return rows as a tuple of the column values. CursorDictRowsMixIn - Causes the cursor to return rows as a dictionary, where the keys are column names and the values are column values. Note that if the column names are not unique, i.e., you are selecting from two @@ -664,10 +705,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. @@ -681,33 +721,6 @@ SSDictCursor Like ``SSCursor`` except it returns rows as dictionaries. -Embedded Server ---------------- - -Instead of connecting to a stand-alone server over the network, -the embedded server support lets you run a full server right in -your Python code or application server. - -If you have built MySQLdb with embedded server support, there -are two additional functions you will need to make use of: - - server_init(args, groups) - Initialize embedded server. If this client is not linked against - the embedded server library, this function does nothing. - - args - sequence of command-line arguments - groups - sequence of groups to use in defaults files - - server_end() - Shut down embedded server. If not using an embedded server, this - does nothing. - -See the MySQL documentation for more information on the embedded -server. - - :Title: MySQLdb: a Python interface for MySQL :Author: Andy Dustman diff --git a/metadata.cfg b/metadata.cfg deleted file mode 100644 index 3f6d5d1b..00000000 --- a/metadata.cfg +++ /dev/null @@ -1,69 +0,0 @@ -[metadata] -version: 1.3.7 -version_info: (1,3,7,'final',1) -description: Python interface to MySQL -long_description: - ========================= - Python interface to MySQL - ========================= - \n - mysqlclient is a fork of MySQL-python. It adds Python 3.3~ support - and merges some pull requests. - \n - MySQLdb is an interface to the popular MySQL_ database server for - Python. The design goals are: - \n - - Compliance with Python database API version 2.0 [PEP-0249]_ - - Thread-safety - - Thread-friendliness (threads will not block each other) - \n - MySQL-4.1 through 5.5 and Python-2.7, 3.3-3.5 are currently - supported. PyPy is supported. - \n - MySQLdb is `Free Software`_. - \n - .. _MySQL: http://www.mysql.com/ - .. _`Free Software`: http://www.gnu.org/ - .. [PEP-0249] https://www.python.org/dev/peps/pep-0249/ -author: Andy Dustman -author_email: farcepest@gmail.com -maintainer: INADA Naoki -maintainer_email: songofacandy@gmail.com -license: GPL -platforms: ALL -url: https://github.com/PyMySQL/mysqlclient-python -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 :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 - Topic :: Database - Topic :: Database :: Database Engines/Servers -py_modules: - _mysql_exceptions - MySQLdb.compat - MySQLdb.converters - MySQLdb.connections - MySQLdb.cursors - MySQLdb.release - MySQLdb.times - MySQLdb.constants.CR - MySQLdb.constants.FIELD_TYPE - MySQLdb.constants.ER - MySQLdb.constants.FLAG - MySQLdb.constants.REFRESH - MySQLdb.constants.CLIENT - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d786f336 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,52 @@ +[project] +name = "mysqlclient" +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 or later (GPLv2+)"} +keywords = ["MySQL"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Other Environment", + "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", + "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", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "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] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +namespaces = false +where = ["src"] +include = ["MySQLdb*"] + +[tool.setuptools.dynamic] +version = {attr = "MySQLdb.release.__version__"} 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" + ] +} 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 diff --git a/samples/waiter_gevent.py b/samples/waiter_gevent.py deleted file mode 100644 index 7ee835f4..00000000 --- a/samples/waiter_gevent.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import print_function -"""Demo using Gevent with mysqlclient.""" - -import gevent.hub -import select -import MySQLdb - - -def gevent_waiter(fd, hub=gevent.hub.get_hub()): - hub.wait(hub.loop.io(fd, 1)) - - -def f(n): - conn = MySQLdb.connect(user='root', waiter=gevent_waiter) - cur = conn.cursor() - cur.execute("SELECT SLEEP(%s)", (n,)) - cur.execute("SELECT 1+%s", (n,)) - print(cur.fetchall()[0]) - - -gevent.spawn(f, 1) -gevent.spawn(f, 2) -gevent.spawn(f, 3) -gevent.spawn(f, 4) -gevent.sleep(5) diff --git a/samples/waiter_meinheld.py b/samples/waiter_meinheld.py deleted file mode 100644 index 838c9153..00000000 --- a/samples/waiter_meinheld.py +++ /dev/null @@ -1,19 +0,0 @@ -import meinheld -import MySQLdb - - -def meinheld_waiter(fd): - meinheld.server.trampoline(fd, read=True, timeout=10) - - -def app(env, start): - cont = b"Hello, World\n" - conn = MySQLdb.connect(user="root", waiter=meinheld_waiter) - cur = conn.cursor() - cur.execute("SELECT SLEEP(2)") - start(b"200 OK", [('Content-Type', 'text/plain'), ('Content-Length', str(len(cont)))]) - return [cont] - - -meinheld.server.listen(("0.0.0.0", 8080)) -meinheld.server.run(app) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5b21f6d2..00000000 --- a/setup.cfg +++ /dev/null @@ -1,19 +0,0 @@ -[test] -test_suite = nose.collector - -[build_ext] -## Only uncomment/set these if the default configuration doesn't work -## Also see http://docs.python.org/dist/setup-config.html -# include-dirs = ? -# library-dirs = ? -# link-objects = ? -# rpath = ? -# libraries = ? - -[bdist_rpm] -doc_files = README MANIFEST doc/*.txt -vendor = MySQL-python SourceForge Project -packager = Andy Dustman -distribution-name = Red Stains Linux -requires = python -build-requires = python-devel mysql-devel zlib-devel openssl-devel diff --git a/setup.py b/setup.py index f88a3819..9a1d26b8 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,169 @@ #!/usr/bin/env python - import os +import subprocess import sys -import distutils.errors import setuptools +from configparser import ConfigParser + + +release_info = {} +with open("src/MySQLdb/release.py", encoding="utf-8") as f: + exec(f.read(), None, release_info) + + +def find_package_name(): + """Get available pkg-config package name""" + # Ubuntu uses mariadb.pc, but CentOS uses libmariadb.pc + packages = ["mysqlclient", "mariadb", "libmariadb", "perconaserverclient"] + 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++" + + 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", "mysql"), + 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 not hasattr(sys, "hexversion") or sys.hexversion < 0x02060000: - raise distutils.errors.DistutilsError("Python 2.6 or newer is required") +if sys.platform == "win32": + ext_options = get_config_win32(get_options()) +else: + ext_options = get_config_posix(get_options()) -if os.name == "posix": - from setup_posix import get_config -else: # assume windows - from setup_windows import get_config +print("# Options for building extension module:") +for k, v in ext_options.items(): + print(f" {k}: {v}") -metadata, options = get_config() -metadata['ext_modules'] = [ - setuptools.Extension(sources=['_mysql.c'], **options)] -metadata['long_description'] = metadata['long_description'].replace(r'\n', '') -setuptools.setup(**metadata) +ext_modules = [ + setuptools.Extension( + "MySQLdb._mysql", + sources=["src/MySQLdb/_mysql.c"], + **ext_options, + ) +] +setuptools.setup(ext_modules=ext_modules) diff --git a/setup_common.py b/setup_common.py deleted file mode 100644 index 03c39bb7..00000000 --- a/setup_common.py +++ /dev/null @@ -1,37 +0,0 @@ -try: - # Python 2.x - from ConfigParser import SafeConfigParser -except ImportError: - # Python 3.x - from configparser import ConfigParser as SafeConfigParser - -def get_metadata_and_options(): - config = SafeConfigParser() - 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("Unknown value %s for option %s" % (value, option)) - -def create_release_file(metadata): - rel = open("MySQLdb/release.py",'w') - rel.write(""" -__author__ = "%(author)s <%(author_email)s>" -version_info = %(version_info)s -__version__ = "%(version)s" -""" % metadata) - rel.close() diff --git a/setup_posix.py b/setup_posix.py deleted file mode 100644 index 320e9e99..00000000 --- a/setup_posix.py +++ /dev/null @@ -1,98 +0,0 @@ -import os, sys -try: - from ConfigParser import SafeConfigParser -except ImportError: - # Probably running Python 3.x - from configparser import ConfigParser as SafeConfigParser - -# This dequote() business is required for some older versions -# of mysql_config - -def dequote(s): - if s[0] in "\"'" and s[0] == s[-1]: - s = s[1:-1] - return s - -def mysql_config(what): - from os import popen - - f = popen("%s --%s" % (mysql_config.path, what)) - data = f.read().strip().split() - ret = f.close() - if ret: - if ret/256: - data = [] - if ret/256 > 1: - raise EnvironmentError("%s not found" % (mysql_config.path,)) - return data -mysql_config.path = "mysql_config" - -def get_config(): - from setup_common import get_metadata_and_options, enabled, create_release_file - - metadata, options = get_metadata_and_options() - - if 'mysql_config' in options: - mysql_config.path = options['mysql_config'] - - extra_objects = [] - static = enabled(options, 'static') - if enabled(options, 'embedded'): - libs = mysql_config("libmysqld-libs") - client = "mysqld" - elif enabled(options, 'threadsafe'): - libs = mysql_config("libs_r") - client = "mysqlclient_r" - if not libs: - libs = mysql_config("libs") - client = "mysqlclient" - else: - libs = mysql_config("libs") - client = "mysqlclient" - - library_dirs = [dequote(i[2:]) for i in libs if i.startswith('-L')] - libraries = [dequote(i[2:]) for i in libs if i.startswith('-l')] - 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] - - # Copy the arch flags for linking as well - for i in range(len(extra_compile_args)): - if extra_compile_args[i] == '-arch': - extra_link_args += ['-arch', extra_compile_args[i + 1]] - - include_dirs = [dequote(i[2:]) - for i in mysql_config('include') if i.startswith('-I')] - - if static: - extra_objects.append(os.path.join(library_dirs[0], 'lib%s.a' % client)) - if client in libraries: - libraries.remove(client) - - name = "mysqlclient" - if enabled(options, 'embedded'): - name = name + "-embedded" - metadata['name'] = name - - define_macros = [ - ('version_info', metadata['version_info']), - ('__version__', metadata['version']), - ] - create_release_file(metadata) - del metadata['version_info'] - ext_options = dict( - name = "_mysql", - library_dirs = library_dirs, - libraries = libraries, - extra_compile_args = extra_compile_args, - extra_link_args = extra_link_args, - include_dirs = include_dirs, - extra_objects = extra_objects, - define_macros = define_macros, - ) - return metadata, ext_options - -if __name__ == "__main__": - sys.stderr.write("""You shouldn't be running this directly; it is used by setup.py.""") diff --git a/setup_windows.py b/setup_windows.py deleted file mode 100644 index 74279077..00000000 --- a/setup_windows.py +++ /dev/null @@ -1,51 +0,0 @@ -import os, sys -from distutils.msvccompiler import get_build_version - - -def get_config(): - from setup_common import get_metadata_and_options, enabled, create_release_file - - metadata, options = get_metadata_and_options() - - connector = options["connector"] - - extra_objects = [] - - if enabled(options, 'embedded'): - client = "mysqld" - else: - client = "mysqlclient" - - vcversion = int(get_build_version()) - library_dirs = [ os.path.join(connector, r'lib\vs%d' % vcversion) ] - libraries = [ 'kernel32', 'advapi32', 'wsock32', client ] - include_dirs = [ os.path.join(connector, r'include') ] - extra_compile_args = [ '/Zl' ] - extra_link_args = ['/MANIFEST'] - - name = "mysqlclient" - if enabled(options, 'embedded'): - name = name + "-embedded" - metadata['name'] = name - - define_macros = [ - ('version_info', metadata['version_info']), - ('__version__', metadata['version']), - ] - create_release_file(metadata) - del metadata['version_info'] - ext_options = dict( - name = "_mysql", - library_dirs = library_dirs, - libraries = libraries, - extra_compile_args = extra_compile_args, - extra_link_args = extra_link_args, - include_dirs = include_dirs, - extra_objects = extra_objects, - define_macros = define_macros, - ) - return metadata, ext_options - -if __name__ == "__main__": - sys.stderr.write("""You shouldn't be running this directly; it is used by setup.py.""") - diff --git a/site.cfg b/site.cfg index 8e2ab991..39e3c2b1 100644 --- a/site.cfg +++ b/site.cfg @@ -1,17 +1,7 @@ [options] -# embedded: link against the embedded server library -# threadsafe: use the threadsafe client -# static: link against a static library (probably required for embedded) - -embedded = False -threadsafe = True +# static: link against a static library static = False -# The path to mysql_config. -# 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 = C:\Program Files (x86)\MySQL\MySQL Connector C 6.1 +connector = diff --git a/src/MySQLdb/__init__.py b/src/MySQLdb/__init__.py new file mode 100644 index 00000000..153bbdfe --- /dev/null +++ b/src/MySQLdb/__init__.py @@ -0,0 +1,168 @@ +""" +MySQLdb - A DB API v2.0 compatible interface to MySQL. + +This package is a wrapper around _mysql, which mostly implements the +MySQL C API. + +connect() -- connects to server + +See the C API specification and the MySQL documentation for more info +on other items. + +For information on how MySQLdb handles type conversion, see the +MySQLdb.converters module. +""" + +from .release import version_info +from . import _mysql + +if version_info != _mysql.version_info: + raise ImportError( + f"this is MySQLdb version {version_info}, " + f"but _mysql is version {_mysql.version_info!r}\n" + f"_mysql: {_mysql.__file__!r}" + ) + + +from ._mysql import ( + NotSupportedError, + OperationalError, + get_client_info, + ProgrammingError, + Error, + InterfaceError, + debug, + IntegrityError, + string_literal, + MySQLError, + DataError, + DatabaseError, + InternalError, + Warning, +) +from MySQLdb.constants import FIELD_TYPE +from MySQLdb.times import ( + Date, + Time, + Timestamp, + DateFromTicks, + TimeFromTicks, + TimestampFromTicks, +) + +threadsafety = 1 +apilevel = "2.0" +paramstyle = "format" + + +class DBAPISet(frozenset): + """A special type of set for which A == x is true if A is a + DBAPISet and x is a member of that set.""" + + def __eq__(self, other): + if isinstance(other, DBAPISet): + return not self.difference(other) + return other in self + + +STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING]) +BINARY = DBAPISet( + [ + FIELD_TYPE.BLOB, + FIELD_TYPE.LONG_BLOB, + FIELD_TYPE.MEDIUM_BLOB, + FIELD_TYPE.TINY_BLOB, + ] +) +NUMBER = DBAPISet( + [ + FIELD_TYPE.DECIMAL, + FIELD_TYPE.DOUBLE, + FIELD_TYPE.FLOAT, + FIELD_TYPE.INT24, + FIELD_TYPE.LONG, + FIELD_TYPE.LONGLONG, + FIELD_TYPE.TINY, + FIELD_TYPE.YEAR, + FIELD_TYPE.NEWDECIMAL, + ] +) +DATE = DBAPISet([FIELD_TYPE.DATE]) +TIME = DBAPISet([FIELD_TYPE.TIME]) +TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) +DATETIME = TIMESTAMP +ROWID = DBAPISet() + + +def test_DBAPISet_set_equality(): + assert STRING == STRING + + +def test_DBAPISet_set_inequality(): + assert STRING != NUMBER + + +def test_DBAPISet_set_equality_membership(): + assert FIELD_TYPE.VAR_STRING == STRING + + +def test_DBAPISet_set_inequality_membership(): + assert FIELD_TYPE.DATE != STRING + + +def Binary(x): + return bytes(x) + + +def Connect(*args, **kwargs): + """Factory function for connections.Connection.""" + from MySQLdb.connections import Connection + + return Connection(*args, **kwargs) + + +connect = Connection = Connect + +__all__ = [ + "BINARY", + "Binary", + "Connect", + "Connection", + "DATE", + "Date", + "Time", + "Timestamp", + "DateFromTicks", + "TimeFromTicks", + "TimestampFromTicks", + "DataError", + "DatabaseError", + "Error", + "FIELD_TYPE", + "IntegrityError", + "InterfaceError", + "InternalError", + "MySQLError", + "NUMBER", + "NotSupportedError", + "DBAPISet", + "OperationalError", + "ProgrammingError", + "ROWID", + "STRING", + "TIME", + "TIMESTAMP", + "Warning", + "apilevel", + "connect", + "connections", + "constants", + "converters", + "cursors", + "debug", + "get_client_info", + "paramstyle", + "string_literal", + "threadsafety", + "version_info", +] diff --git a/_mysql_exceptions.py b/src/MySQLdb/_exceptions.py similarity index 83% rename from _mysql_exceptions.py rename to src/MySQLdb/_exceptions.py index 3241e740..a5aca7e1 100644 --- a/_mysql_exceptions.py +++ b/src/MySQLdb/_exceptions.py @@ -1,87 +1,91 @@ -"""_mysql_exceptions: Exception classes for _mysql and MySQLdb. +"""Exception classes for _mysql and MySQLdb. These classes are dictated by the DB API v2.0: - http://www.python.org/topics/database/DatabaseAPI-2.0.html + https://www.python.org/dev/peps/pep-0249/ """ -try: - from exceptions import Exception, StandardError, Warning -except ImportError: - # Python 3 - StandardError = Exception - -class MySQLError(StandardError): - +class MySQLError(Exception): """Exception related to operation with MySQL.""" + __module__ = "MySQLdb" -class Warning(Warning, MySQLError): +class Warning(Warning, MySQLError): """Exception raised for important warnings like data truncations while inserting, etc.""" -class Error(MySQLError): + __module__ = "MySQLdb" + +class Error(MySQLError): """Exception that is the base class of all other error exceptions (not Warning).""" + __module__ = "MySQLdb" -class InterfaceError(Error): +class InterfaceError(Error): """Exception raised for errors that are related to the database interface rather than the database itself.""" + __module__ = "MySQLdb" -class DatabaseError(Error): +class DatabaseError(Error): """Exception raised for errors that are related to the database.""" + __module__ = "MySQLdb" -class DataError(DatabaseError): +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): +class OperationalError(DatabaseError): """Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer, e.g. an unexpected disconnect occurs, the data source name is not found, a transaction could not be processed, a memory allocation error occurred during processing, etc.""" + __module__ = "MySQLdb" -class IntegrityError(DatabaseError): +class IntegrityError(DatabaseError): """Exception raised when the relational integrity of the database is affected, e.g. a foreign key check fails, duplicate key, etc.""" + __module__ = "MySQLdb" -class InternalError(DatabaseError): +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): +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): +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" diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c new file mode 100644 index 00000000..cd95b641 --- /dev/null +++ b/src/MySQLdb/_mysql.c @@ -0,0 +1,2978 @@ +/* +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. Alternatively, you may use the original license +reproduced below. + +Copyright 1999 by Comstar.net, Inc., Atlanta, GA, US. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Comstar.net, Inc. +or COMSTAR not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. + +COMSTAR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +EVENT SHALL COMSTAR BE LIABLE FOR ANY SPECIAL, INDIRECT OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF +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 +// 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) || \ + (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 + +#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" + +#if PY_MAJOR_VERSION == 2 +#error "Python 2 is not supported" +#endif + +#include "bytesobject.h" +#include "structmember.h" +#include "errmsg.h" + +#define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) +#define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) + +static PyObject *_mysql_MySQLError; +static PyObject *_mysql_Warning; +static PyObject *_mysql_Error; +static PyObject *_mysql_DatabaseError; +static PyObject *_mysql_InterfaceError; +static PyObject *_mysql_DataError; +static PyObject *_mysql_OperationalError; +static PyObject *_mysql_IntegrityError; +static PyObject *_mysql_InternalError; +static PyObject *_mysql_ProgrammingError; +static PyObject *_mysql_NotSupportedError; + +typedef struct { + PyObject_HEAD + MYSQL connection; + bool open; + bool reconnect; + PyObject *converter; +} _mysql_ConnectionObject; + +#define check_connection(c) \ + if (!(c->open)) { \ + return _mysql_Exception(c); \ + }; + +#define result_connection(r) ((_mysql_ConnectionObject *)r->conn) +#define check_result_connection(r) check_connection(result_connection(r)) + +extern PyTypeObject _mysql_ConnectionObject_Type; + +typedef struct { + PyObject_HEAD + PyObject *conn; + MYSQL_RES *result; + int nfields; + int use; + char has_next; + PyObject *converter; + const char *encoding; +} _mysql_ResultObject; + +extern PyTypeObject _mysql_ResultObject_Type; + + +PyObject * +_mysql_Exception(_mysql_ConnectionObject *c) +{ + PyObject *t, *e; + int merr; + + if (!(t = PyTuple_New(2))) return NULL; + if (!(c->open)) { + /* GH-270: When connection is closed, accessing the c->connection + * object may cause SEGV. + */ + merr = CR_SERVER_GONE_ERROR; + } + else { + merr = mysql_errno(&(c->connection)); + } + switch (merr) { + case 0: + e = _mysql_InterfaceError; + break; + case CR_COMMANDS_OUT_OF_SYNC: + case ER_DB_CREATE_EXISTS: + case ER_SYNTAX_ERROR: + case ER_PARSE_ERROR: + case ER_NO_SUCH_TABLE: + case ER_WRONG_DB_NAME: + case ER_WRONG_TABLE_NAME: + case ER_FIELD_SPECIFIED_TWICE: + case ER_INVALID_GROUP_FUNC_USE: + case ER_UNSUPPORTED_EXTENSION: + case ER_TABLE_MUST_HAVE_COLUMNS: +#ifdef ER_CANT_DO_THIS_DURING_AN_TRANSACTION + case ER_CANT_DO_THIS_DURING_AN_TRANSACTION: +#endif + e = _mysql_ProgrammingError; + break; +#ifdef WARN_DATA_TRUNCATED + case WARN_DATA_TRUNCATED: +#ifdef WARN_NULL_TO_NOTNULL + case WARN_NULL_TO_NOTNULL: +#endif +#ifdef ER_WARN_DATA_OUT_OF_RANGE + case ER_WARN_DATA_OUT_OF_RANGE: +#endif +#ifdef ER_NO_DEFAULT + case ER_NO_DEFAULT: +#endif +#ifdef ER_PRIMARY_CANT_HAVE_NULL + case ER_PRIMARY_CANT_HAVE_NULL: +#endif +#ifdef ER_DATA_TOO_LONG + case ER_DATA_TOO_LONG: +#endif +#ifdef ER_DATETIME_FUNCTION_OVERFLOW + case ER_DATETIME_FUNCTION_OVERFLOW: +#endif + e = _mysql_DataError; + break; +#endif + case ER_DUP_ENTRY: +#ifdef ER_DUP_UNIQUE + case ER_DUP_UNIQUE: +#endif +#ifdef ER_NO_REFERENCED_ROW + case ER_NO_REFERENCED_ROW: +#endif +#ifdef ER_NO_REFERENCED_ROW_2 + case ER_NO_REFERENCED_ROW_2: +#endif +#ifdef ER_ROW_IS_REFERENCED + case ER_ROW_IS_REFERENCED: +#endif +#ifdef ER_ROW_IS_REFERENCED_2 + case ER_ROW_IS_REFERENCED_2: +#endif +#ifdef ER_CANNOT_ADD_FOREIGN + case ER_CANNOT_ADD_FOREIGN: +#endif +#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 + case ER_WARNING_NOT_COMPLETE_ROLLBACK: +#endif +#ifdef ER_NOT_SUPPORTED_YET + case ER_NOT_SUPPORTED_YET: +#endif +#ifdef ER_FEATURE_DISABLED + case ER_FEATURE_DISABLED: +#endif +#ifdef ER_UNKNOWN_STORAGE_ENGINE + case ER_UNKNOWN_STORAGE_ENGINE: +#endif + e = _mysql_NotSupportedError; + break; + default: + if (merr < 1000) + e = _mysql_InternalError; + else + e = _mysql_OperationalError; + break; + } + PyTuple_SET_ITEM(t, 0, PyLong_FromLong((long)merr)); + PyTuple_SET_ITEM(t, 1, PyUnicode_FromString(mysql_error(&(c->connection)))); + PyErr_SetObject(e, t); + Py_DECREF(t); + return NULL; +} + +static const char *utf8 = "utf8"; + +static const char* +_get_encoding(MYSQL *mysql) +{ + MY_CHARSET_INFO cs; + mysql_get_character_set_info(mysql, &cs); + if (strncmp(utf8, cs.csname, 4) == 0) { // utf8, utf8mb3, utf8mb4 + return utf8; + } + else if (strncmp("latin1", cs.csname, 6) == 0) { + return "cp1252"; + } + else if (strncmp("koi8r", cs.csname, 5) == 0) { + return "koi8_r"; + } + else if (strncmp("koi8u", cs.csname, 5) == 0) { + return "koi8_u"; + } + return cs.csname; +} + +static char _mysql_ResultObject__doc__[] = +"result(connection, use=0, converter={}) -- Result set from a query.\n\ +\n\ +Creating instances of this class directly is an excellent way to\n\ +shoot yourself in the foot. If using _mysql.connection directly,\n\ +use connection.store_result() or connection.use_result() instead.\n\ +If using MySQLdb.Connection, this is done by the cursor class.\n\ +Just forget you ever saw this. Forget... FOR-GET..."; + +static int +_mysql_ResultObject_Initialize( + _mysql_ResultObject *self, + PyObject *args, + PyObject *kwargs) +{ + static char *kwlist[] = {"connection", "use", "converter", NULL}; + MYSQL_RES *result; + _mysql_ConnectionObject *conn=NULL; + int use=0; + PyObject *conv=NULL; + int n, i; + MYSQL_FIELD *fields; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|iO", kwlist, + &_mysql_ConnectionObject_Type, &conn, &use, &conv)) + return -1; + + self->conn = (PyObject *) conn; + Py_INCREF(conn); + self->use = use; + Py_BEGIN_ALLOW_THREADS ; + if (use) + result = mysql_use_result(&(conn->connection)); + else + result = mysql_store_result(&(conn->connection)); + self->result = result; + self->has_next = (char)mysql_more_results(&(conn->connection)); + Py_END_ALLOW_THREADS ; + + self->encoding = _get_encoding(&(conn->connection)); + //fprintf(stderr, "encoding=%s\n", self->encoding); + if (!result) { + if (mysql_errno(&(conn->connection))) { + _mysql_Exception(conn); + return -1; + } + self->converter = PyTuple_New(0); + return 0; + } + n = mysql_num_fields(result); + self->nfields = n; + if (!(self->converter = PyTuple_New(n))) { + return -1; + } + fields = mysql_fetch_fields(result); + for (i=0; iconverter, i, fun); + } + + return 0; +} + +static int _mysql_ResultObject_traverse( + _mysql_ResultObject *self, + visitproc visit, + void *arg) +{ + int r; + if (self->converter) { + if (!(r = visit(self->converter, arg))) return r; + } + if (self->conn) + return visit(self->conn, arg); + return 0; +} + +static int _mysql_ResultObject_clear(_mysql_ResultObject *self) +{ + Py_CLEAR(self->converter); + Py_CLEAR(self->conn); + return 0; +} + +enum { + SSLMODE_DISABLED = 1, + SSLMODE_PREFERRED = 2, + SSLMODE_REQUIRED = 3, + SSLMODE_VERIFY_CA = 4, + SSLMODE_VERIFY_IDENTITY = 5 +}; + +static int +_get_ssl_mode_num(const char *ssl_mode) +{ + 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) { + // SSL_MODE one-based + return i + 1; + } + } + return -1; +} + +static int +_mysql_ConnectionObject_Initialize( + _mysql_ConnectionObject *self, + PyObject *args, + PyObject *kwargs) +{ + MYSQL *conn = NULL; + PyObject *conv = NULL; + PyObject *ssl = NULL; + const char *ssl_mode = NULL; + const char *key = NULL, *cert = NULL, *ca = NULL, + *capath = NULL, *cipher = NULL; + PyObject *ssl_keepref[5] = {NULL}; + int n_ssl_keepref = 0; + char *host = NULL, *user = NULL, *passwd = NULL, + *db = NULL, *unix_socket = NULL; + unsigned int port = 0; + unsigned int client_flag = 0; + static char *kwlist[] = { "host", "user", "password", "database", "port", + "unix_socket", "conv", + "connect_timeout", "compress", + "named_pipe", "init_command", + "read_default_file", "read_default_group", + "client_flag", "ssl", "ssl_mode", + "local_infile", + "read_timeout", "write_timeout", "charset", + "auth_plugin", "server_public_key_path", "local_infile_dir", + NULL } ; + int connect_timeout = 0; + int read_timeout = 0; + int write_timeout = 0; + int compress = -1, named_pipe = -1, local_infile = -1; + int ssl_mode_num = SSLMODE_PREFERRED; + char *init_command=NULL, + *read_default_file=NULL, + *read_default_group=NULL, + *charset=NULL, + *auth_plugin=NULL, + *server_public_key_path=NULL, + *local_infile_dir=NULL; + + self->converter = NULL; + self->open = false; + self->reconnect = false; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "|ssssisOiiisssiOsiiissss:connect", + kwlist, + &host, &user, &passwd, &db, + &port, &unix_socket, &conv, + &connect_timeout, + &compress, &named_pipe, + &init_command, &read_default_file, + &read_default_group, + &client_flag, &ssl, &ssl_mode, + &local_infile, + &read_timeout, + &write_timeout, + &charset, + &auth_plugin, + &server_public_key_path, + &local_infile_dir + )) + 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 + +#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);\ + if(t){d=PyUnicode_AsUTF8(t);ssl_keepref[n_ssl_keepref++]=t;}\ + PyErr_Clear();} + + char ssl_mode_set = 0; + if (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)); + if (!conn) { + PyErr_SetNone(PyExc_MemoryError); + return -1; + } + self->open = true; + + if (connect_timeout) { + unsigned int timeout = connect_timeout; + mysql_options(&(self->connection), MYSQL_OPT_CONNECT_TIMEOUT, + (char *)&timeout); + } + if (read_timeout) { + unsigned int timeout = read_timeout; + mysql_options(&(self->connection), MYSQL_OPT_READ_TIMEOUT, + (char *)&timeout); + } + if (write_timeout) { + unsigned int timeout = write_timeout; + mysql_options(&(self->connection), MYSQL_OPT_WRITE_TIMEOUT, + (char *)&timeout); + } + if (compress != -1) { + mysql_options(&(self->connection), MYSQL_OPT_COMPRESS, 0); + client_flag |= CLIENT_COMPRESS; + } + if (named_pipe != -1) + mysql_options(&(self->connection), MYSQL_OPT_NAMED_PIPE, 0); + if (init_command != NULL) + mysql_options(&(self->connection), MYSQL_INIT_COMMAND, init_command); + if (read_default_file != NULL) + mysql_options(&(self->connection), MYSQL_READ_DEFAULT_FILE, read_default_file); + if (read_default_group != NULL) + mysql_options(&(self->connection), MYSQL_READ_DEFAULT_GROUP, read_default_group); + + if (local_infile != -1) + mysql_options(&(self->connection), MYSQL_OPT_LOCAL_INFILE, (char *) &local_infile); + + if (ssl) { + mysql_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); + } + for (int i=0 ; iconnection), MYSQL_OPT_SSL_MODE, &ssl_mode_num); + } +#else + // 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_options(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_true); + } else { + mysql_options(&(self->connection), MYSQL_OPT_SSL_ENFORCE, (void *)&my_false); + } + if (ssl_mode_num >= SSLMODE_VERIFY_CA) { + mysql_options(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_true); + } else { + mysql_options(&(self->connection), MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&my_false); + } +#endif + + if (charset) { + mysql_options(&(self->connection), MYSQL_SET_CHARSET_NAME, charset); + } + 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 + +#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); + Py_END_ALLOW_THREADS + + if (!conn) { + _mysql_Exception(self); + return -1; + } + + /* Internal references to python-land objects */ + if (!conv) + conv = PyDict_New(); + else + Py_INCREF(conv); + + if (!conv) + return -1; + self->converter = conv; + + /* + PyType_GenericAlloc() automatically sets up GC allocation and + tracking for GC objects, at least in 2.2.1, so it does not need to + be done here. tp_dealloc still needs to call PyObject_GC_UnTrack(), + however. + */ + return 0; +} + +static char _mysql_connect__doc__[] = +"Returns a MYSQL connection object. Exclusive use of\n\ +keyword parameters strongly recommended. Consult the\n\ +MySQL C API documentation for more details.\n\ +\n\ +host\n\ + string, host to connect\n\ +\n\ +user\n\ + string, user to connect as\n\ +\n\ +password\n\ + string, password to use\n\ +\n\ +database\n\ + string, database to use\n\ +\n\ +port\n\ + integer, TCP/IP port to connect to\n\ +\n\ +unix_socket\n\ + string, location of unix_socket (UNIX-ish only)\n\ +\n\ +conv\n\ + mapping, maps MySQL FIELD_TYPE.* to Python functions which\n\ + convert a string to the appropriate Python type\n\ +\n\ +connect_timeout\n\ + number of seconds to wait before the connection\n\ + attempt fails.\n\ +\n\ +compress\n\ + if set, gzip compression is enabled\n\ +\n\ +named_pipe\n\ + if set, connect to server via named pipe (Windows only)\n\ +\n\ +init_command\n\ + command which is run once the connection is created\n\ +\n\ +read_default_file\n\ + see the MySQL documentation for mysql_options()\n\ +\n\ +read_default_group\n\ + see the MySQL documentation for mysql_options()\n\ +\n\ +client_flag\n\ + client flags from MySQLdb.constants.CLIENT\n\ +\n\ +load_infile\n\ + int, non-zero enables LOAD LOCAL INFILE, zero disables\n\ +\n\ +"; + +static PyObject * +_mysql_connect( + PyObject *self, + PyObject *args, + PyObject *kwargs) +{ + _mysql_ConnectionObject *c=NULL; + + c = MyAlloc(_mysql_ConnectionObject, _mysql_ConnectionObject_Type); + if (c == NULL) return NULL; + if (_mysql_ConnectionObject_Initialize(c, args, kwargs)) { + Py_DECREF(c); + c = NULL; + } + return (PyObject *) c; +} + +static int _mysql_ConnectionObject_traverse( + _mysql_ConnectionObject *self, + visitproc visit, + void *arg) +{ + if (self->converter) + return visit(self->converter, arg); + return 0; +} + +static int _mysql_ConnectionObject_clear( + _mysql_ConnectionObject *self) +{ + Py_XDECREF(self->converter); + self->converter = NULL; + return 0; +} + +static char _mysql_ConnectionObject_close__doc__[] = +"Close the connection. No further activity possible."; + +static PyObject * +_mysql_ConnectionObject_close( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + Py_BEGIN_ALLOW_THREADS + mysql_close(&(self->connection)); + Py_END_ALLOW_THREADS + self->open = false; + _mysql_ConnectionObject_clear(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_affected_rows__doc__ [] = +"Return number of rows affected by the last query.\n\ +Non-standard. Use Cursor.rowcount.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_affected_rows( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + my_ulonglong ret; + check_connection(self); + ret = mysql_affected_rows(&(self->connection)); + if (ret == (my_ulonglong)-1) + return PyLong_FromLong(-1); + return PyLong_FromUnsignedLongLong(ret); +} + +static char _mysql_debug__doc__[] = +"Does a DBUG_PUSH with the given string.\n\ +mysql_debug() uses the Fred Fish debug library.\n\ +To use this function, you must compile the client library to\n\ +support debugging.\n\ +"; +static PyObject * +_mysql_debug( + PyObject *self, + PyObject *args) +{ + char *debug; + if (!PyArg_ParseTuple(args, "s", &debug)) return NULL; + mysql_debug(debug); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_dump_debug_info__doc__[] = +"Instructs the server to write some debug information to the\n\ +log. The connected user must have the process privilege for\n\ +this to work. Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_dump_debug_info( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_dump_debug_info(&(self->connection)); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_autocommit__doc__[] = +"Set the autocommit mode. True values enable; False value disable.\n\ +"; +static PyObject * +_mysql_ConnectionObject_autocommit( + _mysql_ConnectionObject *self, + PyObject *args) +{ + int flag, err; + if (!PyArg_ParseTuple(args, "i", &flag)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_autocommit(&(self->connection), flag); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_get_autocommit__doc__[] = +"Get the autocommit mode. True when enable; False when disable.\n"; + +static PyObject * +_mysql_ConnectionObject_get_autocommit( + _mysql_ConnectionObject *self, + PyObject *args) +{ + check_connection(self); + if (self->connection.server_status & SERVER_STATUS_AUTOCOMMIT) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static char _mysql_ConnectionObject_commit__doc__[] = +"Commits the current transaction\n\ +"; +static PyObject * +_mysql_ConnectionObject_commit( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_commit(&(self->connection)); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_rollback__doc__[] = +"Rolls back the current transaction\n\ +"; +static PyObject * +_mysql_ConnectionObject_rollback( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_rollback(&(self->connection)); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_next_result__doc__[] = +"If more query results exist, next_result() reads the next query\n\ +results and returns the status back to application.\n\ +\n\ +After calling next_result() the state of the connection is as if\n\ +you had called query() for the next query. This means that you can\n\ +now call store_result(), warning_count(), affected_rows()\n\ +, and so forth. \n\ +\n\ +Returns 0 if there are more results; -1 if there are no more results\n\ +\n\ +Non-standard.\n\ +"; +static PyObject * +_mysql_ConnectionObject_next_result( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int err; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_next_result(&(self->connection)); + Py_END_ALLOW_THREADS + if (err > 0) return _mysql_Exception(self); + return PyLong_FromLong(err); +} + + +static char _mysql_ConnectionObject_set_server_option__doc__[] = +"set_server_option(option) -- Enables or disables an option\n\ +for the connection.\n\ +\n\ +Non-standard.\n\ +"; +static PyObject * +_mysql_ConnectionObject_set_server_option( + _mysql_ConnectionObject *self, + PyObject *args) +{ + int err, flags=0; + if (!PyArg_ParseTuple(args, "i", &flags)) + return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_set_server_option(&(self->connection), flags); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + return PyLong_FromLong(err); +} + +static char _mysql_ConnectionObject_sqlstate__doc__[] = +"Returns a string containing the SQLSTATE error code\n\ +for the last error. The error code consists of five characters.\n\ +'00000' means \"no error.\" The values are specified by ANSI SQL\n\ +and ODBC. For a list of possible values, see section 23\n\ +Error Handling in MySQL in the MySQL Manual.\n\ +\n\ +Note that not all MySQL errors are yet mapped to SQLSTATE's.\n\ +The value 'HY000' (general error) is used for unmapped errors.\n\ +\n\ +Non-standard.\n\ +"; +static PyObject * +_mysql_ConnectionObject_sqlstate( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyUnicode_FromString(mysql_sqlstate(&(self->connection))); +} + +static char _mysql_ConnectionObject_warning_count__doc__[] = +"Returns the number of warnings generated during execution\n\ +of the previous SQL statement.\n\ +\n\ +Non-standard.\n\ +"; +static PyObject * +_mysql_ConnectionObject_warning_count( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyLong_FromLong(mysql_warning_count(&(self->connection))); +} + +static char _mysql_ConnectionObject_errno__doc__[] = +"Returns the error code for the most recently invoked API function\n\ +that can succeed or fail. A return value of zero means that no error\n\ +occurred.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_errno( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyLong_FromLong((long)mysql_errno(&(self->connection))); +} + +static char _mysql_ConnectionObject_error__doc__[] = +"Returns the error message for the most recently invoked API function\n\ +that can succeed or fail. An empty string ("") is returned if no error\n\ +occurred.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_error( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyUnicode_FromString(mysql_error(&(self->connection))); +} + +static char _mysql_escape_string__doc__[] = +"escape_string(s) -- quote any SQL-interpreted characters in string s.\n\ +\n\ +Use connection.escape_string(s), if you use it at all.\n\ +_mysql.escape_string(s) cannot handle character sets. You are\n\ +probably better off using connection.escape(o) instead, since\n\ +it will escape entire sequences as well as strings."; + +static PyObject * +_mysql_escape_string( + _mysql_ConnectionObject *self, + PyObject *args) +{ + PyObject *str; + char *in, *out; + 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); + if (!str) return PyErr_NoMemory(); + out = PyBytes_AS_STRING(str); + + if (self && PyModule_Check((PyObject*)self)) + self = NULL; + if (self && self->open) { +#if MYSQL_VERSION_ID >= 50707 && !defined(MARIADB_BASE_VERSION) && !defined(MARIADB_VERSION_ID) + len = mysql_real_escape_string_quote(&(self->connection), out, in, size, '\''); +#else + len = mysql_real_escape_string(&(self->connection), out, in, size); +#endif + } else { + len = mysql_escape_string(out, in, size); + } + if (_PyBytes_Resize(&str, len) < 0) return NULL; + return (str); +} + +static char _mysql_string_literal__doc__[] = +"string_literal(obj) -- converts object obj into a SQL string literal.\n\ +This means, any special SQL characters are escaped, and it is enclosed\n\ +within single quotes. In other words, it performs:\n\ +\n\ +\"'%s'\" % escape_string(str(obj))\n\ +\n\ +Use connection.string_literal(obj), if you use it at all.\n\ +_mysql.string_literal(obj) cannot handle character sets."; + +static PyObject * +_mysql_string_literal( + _mysql_ConnectionObject *self, + PyObject *o) +{ + PyObject *s; // input string or bytes. need to decref. + + if (self && PyModule_Check((PyObject*)self)) + self = NULL; + + if (PyBytes_Check(o)) { + s = o; + Py_INCREF(s); + } + 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; + } + } + + // 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(); + } + 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, '\''); +#else + len = mysql_real_escape_string(&(self->connection), out+1, in, size); +#endif + } else { + len = mysql_escape_string(out+1, in, size); + } + + Py_DECREF(s); + *out = *(out+len+1) = '\''; + if (_PyBytes_Resize(&str, len+2) < 0) { + Py_DECREF(str); + return NULL; + } + return str; +} + +static PyObject * +_escape_item( + PyObject *self, + PyObject *item, + PyObject *d) +{ + PyObject *quoted=NULL, *itemtype, *itemconv; + if (!(itemtype = PyObject_Type(item))) { + return NULL; + } + itemconv = PyObject_GetItem(d, itemtype); + Py_DECREF(itemtype); + if (!itemconv) { + PyErr_Clear(); + return _mysql_string_literal((_mysql_ConnectionObject*)self, item); + } + Py_INCREF(d); + quoted = PyObject_CallFunction(itemconv, "OO", item, d); + Py_DECREF(d); + Py_DECREF(itemconv); + + return quoted; +} + +static char _mysql_escape__doc__[] = +"escape(obj, dict) -- escape any special characters in object obj\n\ +using mapping dict to provide quoting functions for each type.\n\ +Returns a SQL literal string."; +static PyObject * +_mysql_escape( + PyObject *self, + PyObject *args) +{ + PyObject *o=NULL, *d=NULL; + if (!PyArg_ParseTuple(args, "O|O:escape", &o, &d)) + return NULL; + if (d) { + if (!PyMapping_Check(d)) { + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a mapping"); + return NULL; + } + return _escape_item(self, o, d); + } else { + if (!self) { + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a mapping"); + return NULL; + } + return _escape_item(self, o, + ((_mysql_ConnectionObject *) self)->converter); + } +} + +static char _mysql_ResultObject_describe__doc__[] = +"Returns the sequence of 7-tuples required by the DB-API for\n\ +the Cursor.description attribute.\n\ +"; + +static PyObject * +_mysql_ResultObject_describe( + _mysql_ResultObject *self, + PyObject *noargs) +{ + PyObject *d; + MYSQL_FIELD *fields; + unsigned int i, n; + + check_result_connection(self); + + n = mysql_num_fields(self->result); + fields = mysql_fetch_fields(self->result); + if (!(d = PyTuple_New(n))) return NULL; + for (i=0; iencoding == utf8) { + name = PyUnicode_DecodeUTF8(fields[i].name, fields[i].name_length, "replace"); + } else { + name = PyUnicode_Decode(fields[i].name, fields[i].name_length, self->encoding, "replace"); + } + if (name == NULL) { + goto error; + } + + t = Py_BuildValue("(Niiiiii)", + name, + (long) fields[i].type, + (long) fields[i].max_length, + (long) fields[i].length, + (long) fields[i].length, + (long) fields[i].decimals, + (long) !(IS_NOT_NULL(fields[i].flags))); + if (!t) goto error; + PyTuple_SET_ITEM(d, i, t); + } + return d; + error: + Py_XDECREF(d); + return NULL; +} + +static char _mysql_ResultObject_field_flags__doc__[] = +"Returns a tuple of field flags, one for each column in the result.\n\ +" ; + +static PyObject * +_mysql_ResultObject_field_flags( + _mysql_ResultObject *self, + PyObject *noargs) +{ + PyObject *d; + MYSQL_FIELD *fields; + unsigned int i, n; + check_result_connection(self); + n = mysql_num_fields(self->result); + fields = mysql_fetch_fields(self->result); + if (!(d = PyTuple_New(n))) return NULL; + for (i=0; itype) { + case FIELD_TYPE_DECIMAL: + case FIELD_TYPE_NEWDECIMAL: + case FIELD_TYPE_TIMESTAMP: + case FIELD_TYPE_DATETIME: + case FIELD_TYPE_TIME: + case FIELD_TYPE_DATE: + binary = 0; // pass str, because these converters expect it + break; + default: // Default to just passing bytes + binary = 1; + } + return PyObject_CallFunction(converter, + binary ? "y#" : "s#", + rowitem, (Py_ssize_t)length); +} + +static PyObject * +_mysql_row_to_tuple( + _mysql_ResultObject *self, + MYSQL_ROW row, + PyObject *unused) +{ + unsigned int n, i; + unsigned long *length; + PyObject *r, *c; + MYSQL_FIELD *fields; + + n = mysql_num_fields(self->result); + if (!(r = PyTuple_New(n))) return NULL; + length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); + for (i=0; iconverter, i); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); + if (!v) goto error; + PyTuple_SET_ITEM(r, i, v); + } + return r; + error: + Py_XDECREF(r); + return NULL; +} + +static PyObject * +_mysql_row_to_dict( + _mysql_ResultObject *self, + MYSQL_ROW row, + PyObject *cache) +{ + unsigned int n, i; + unsigned long *length; + PyObject *r, *c; + MYSQL_FIELD *fields; + + n = mysql_num_fields(self->result); + if (!(r = PyDict_New())) return NULL; + length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); + for (i=0; iconverter, i); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); + if (!v) goto error; + + PyObject *pyname = PyUnicode_FromString(fields[i].name); + if (pyname == NULL) { + Py_DECREF(v); + goto error; + } + int err = PyDict_Contains(r, pyname); + if (err < 0) { // error + Py_DECREF(v); + goto error; + } + 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; + } + } + + err = PyDict_SetItem(r, pyname, v); + if (cache) { + PyTuple_SET_ITEM(cache, i, pyname); + } else { + Py_DECREF(pyname); + } + Py_DECREF(v); + if (err) { + goto error; + } + } + return r; +error: + Py_DECREF(r); + return NULL; +} + +static PyObject * +_mysql_row_to_dict_old( + _mysql_ResultObject *self, + MYSQL_ROW row, + PyObject *cache) +{ + unsigned int n, i; + unsigned long *length; + PyObject *r, *c; + MYSQL_FIELD *fields; + + n = mysql_num_fields(self->result); + if (!(r = PyDict_New())) return NULL; + length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); + for (i=0; iconverter, i); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding); + 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(v); + if (cache) { + PyTuple_SET_ITEM(cache, i, pyname); + } else { + Py_DECREF(pyname); + } + if (err) { + goto error; + } + } + return r; + error: + Py_XDECREF(r); + return NULL; +} + +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, + 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; + if (!self->use) + row = mysql_fetch_row(self->result); + else { + Py_BEGIN_ALLOW_THREADS + row = mysql_fetch_row(self->result); + Py_END_ALLOW_THREADS + } + if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) { + _mysql_Exception((_mysql_ConnectionObject *)self->conn); + goto error; + } + if (!row) { + break; + } + 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); + goto error; + } + Py_DECREF(v); + } + Py_XDECREF(cache); + return i; +error: + Py_XDECREF(cache); + return -1; +} + +static char _mysql_ResultObject_fetch_row__doc__[] = +"fetch_row([maxrows, how]) -- Fetches up to maxrows as a tuple.\n\ +The rows are formatted according to how:\n\ +\n\ + 0 -- tuples (default)\n\ + 1 -- dictionaries, key=column or table.column if duplicated\n\ + 2 -- dictionaries, key=table.column\n\ +"; + +static PyObject * +_mysql_ResultObject_fetch_row( + _mysql_ResultObject *self, + PyObject *args, + PyObject *kwargs) +{ + static char *kwlist[] = {"maxrows", "how", NULL }; + int maxrows=1, how=0; + PyObject *r=NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii:fetch_row", kwlist, + &maxrows, &how)) + return NULL; + check_result_connection(self); + if (how >= (int)(sizeof(row_converters) / sizeof(row_converters[0]))) { + PyErr_SetString(PyExc_ValueError, "how out of range"); + return NULL; + } + if (!maxrows) { + if (self->use) { + maxrows = INT_MAX; + } else { + // todo: preallocate. + maxrows = (Py_ssize_t) mysql_num_rows(self->result); + } + } + if (!(r = PyList_New(0))) goto error; + Py_ssize_t rowsadded = _mysql__fetch_row(self, r, maxrows, how); + 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; +} + +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; + Py_BEGIN_ALLOW_THREADS + while (NULL != (row = mysql_fetch_row(self->result))) { + // do nothing + } + Py_END_ALLOW_THREADS + _mysql_ConnectionObject *conn = (_mysql_ConnectionObject *)self->conn; + if (mysql_errno(&conn->connection)) { + return _mysql_Exception(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\ +specified by mysql. In subsequent queries, this database is\n\ +the default for table references that do not include an\n\ +explicit database specifier.\n\ +\n\ +This function was introduced in MySQL Version 3.23.3.\n\ +\n\ +Fails unless the connected user can be authenticated or if he\n\ +doesn't have permission to use the database. In this case the\n\ +user and database are not changed.\n\ +\n\ +The db parameter may be set to None if you don't want to have\n\ +a default database.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_change_user( + _mysql_ConnectionObject *self, + PyObject *args, + PyObject *kwargs) +{ + char *user, *pwd=NULL, *db=NULL; + int r; + static char *kwlist[] = { "user", "passwd", "db", NULL } ; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ss:change_user", + kwlist, &user, &pwd, &db)) + return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_change_user(&(self->connection), user, pwd, db); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_character_set_name__doc__[] = +"Returns the default character set for the current connection.\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_character_set_name( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + const char *s; + check_connection(self); + s = mysql_character_set_name(&(self->connection)); + return PyUnicode_FromString(s); +} + +static char _mysql_ConnectionObject_set_character_set__doc__[] = +"Sets the default character set for the current connection.\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_set_character_set( + _mysql_ConnectionObject *self, + PyObject *args) +{ + const char *s; + int err; + if (!PyArg_ParseTuple(args, "s", &s)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_set_character_set(&(self->connection), s); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +#if MYSQL_VERSION_ID >= 50010 +static char _mysql_ConnectionObject_get_character_set_info__doc__[] = +"Returns a dict with information about the current character set:\n\ +\n\ +collation\n\ + collation name\n\ +name\n\ + character set name\n\ +comment\n\ + comment or descriptive name\n\ +dir\n\ + character set directory\n\ +mbminlen\n\ + min. length for multibyte string\n\ +mbmaxlen\n\ + max. length for multibyte string\n\ +\n\ +Not all keys may be present, particularly dir.\n\ +\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_character_set_info( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *result; + MY_CHARSET_INFO cs; + + check_connection(self); + mysql_get_character_set_info(&(self->connection), &cs); + if (!(result = PyDict_New())) return NULL; + if (cs.csname) + PyDict_SetItemString(result, "name", PyUnicode_FromString(cs.csname)); + if (cs.name) + PyDict_SetItemString(result, "collation", PyUnicode_FromString(cs.name)); + if (cs.comment) + PyDict_SetItemString(result, "comment", PyUnicode_FromString(cs.comment)); + if (cs.dir) + PyDict_SetItemString(result, "dir", PyUnicode_FromString(cs.dir)); + PyDict_SetItemString(result, "mbminlen", PyLong_FromLong(cs.mbminlen)); + PyDict_SetItemString(result, "mbmaxlen", PyLong_FromLong(cs.mbmaxlen)); + return result; +} +#endif + +static char _mysql_ConnectionObject_get_native_connection__doc__[] = +"Return the internal MYSQL* wrapped in a PyCapsule object.\n\ +NOTE: this is a private API introduced ONLY for XTA integration,\n\ + don't use it for different use cases.\n\ + This method is supported only for XTA integration and support must\n\ + be asked to LIXA project: http://www.tiian.org/lixa/\n\ + Please DO NOT ask support to PyMySQL/mysqlclient-python project.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_native_connection( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *result; + check_connection(self); + result = PyCapsule_New(&(self->connection), + "_mysql.connection.native_connection", NULL); + return result; +} + + +static char _mysql_get_client_info__doc__[] = +"get_client_info() -- Returns a string that represents\n\ +the client library version."; +static PyObject * +_mysql_get_client_info( + PyObject *self, + PyObject *noargs) +{ + return PyUnicode_FromString(mysql_get_client_info()); +} + +static char _mysql_ConnectionObject_get_host_info__doc__[] = +"Returns a string that represents the MySQL client library\n\ +version. Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_host_info( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyUnicode_FromString(mysql_get_host_info(&(self->connection))); +} + +static char _mysql_ConnectionObject_get_proto_info__doc__[] = +"Returns an unsigned integer representing the protocol version\n\ +used by the current connection. Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_proto_info( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyLong_FromLong((long)mysql_get_proto_info(&(self->connection))); +} + +static char _mysql_ConnectionObject_get_server_info__doc__[] = +"Returns a string that represents the server version number.\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_server_info( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyUnicode_FromString(mysql_get_server_info(&(self->connection))); +} + +static char _mysql_ConnectionObject_info__doc__[] = +"Retrieves a string providing information about the most\n\ +recently executed query. Non-standard. Use messages or\n\ +Cursor.messages.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_info( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + const char *s; + check_connection(self); + s = mysql_info(&(self->connection)); + if (s) return PyUnicode_FromString(s); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_insert_id__doc__[] = +"Returns the ID generated for an AUTO_INCREMENT column by the previous\n\ +query. Use this function after you have performed an INSERT query into a\n\ +table that contains an AUTO_INCREMENT field.\n\ +\n\ +Note that this returns 0 if the previous query does not\n\ +generate an AUTO_INCREMENT value. If you need to save the value for\n\ +later, be sure to call this immediately after the query\n\ +that generates the value.\n\ +\n\ +The ID is updated after INSERT and UPDATE statements that generate\n\ +an AUTO_INCREMENT value or that set a column value to\n\ +LAST_INSERT_ID(expr). See section 6.3.5.2 Miscellaneous Functions\n\ +in the MySQL documentation.\n\ +\n\ +Also note that the value of the SQL LAST_INSERT_ID() function always\n\ +contains the most recently generated AUTO_INCREMENT value, and is not\n\ +reset between queries because the value of that function is maintained\n\ +in the server.\n\ +" ; + +static PyObject * +_mysql_ConnectionObject_insert_id( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + my_ulonglong r; + check_connection(self); + r = mysql_insert_id(&(self->connection)); + return PyLong_FromUnsignedLongLong(r); +} + +static char _mysql_ConnectionObject_kill__doc__[] = +"Asks the server to kill the thread specified by pid.\n\ +Non-standard. Deprecated."; + +static PyObject * +_mysql_ConnectionObject_kill( + _mysql_ConnectionObject *self, + PyObject *args) +{ + unsigned long pid; + int r; + 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), query); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_field_count__doc__[] = +"Returns the number of columns for the most recent query on the\n\ +connection. Non-standard. Will probably give you bogus results\n\ +on most cursor classes. Use Cursor.rowcount.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_field_count( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyLong_FromLong((long)mysql_field_count(&(self->connection))); +} + +static char _mysql_ConnectionObject_fileno__doc__[] = +"Return file descriptor of the underlying libmysqlclient connection.\n\ +This provides raw access to the underlying network connection.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_fileno( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + return PyLong_FromLong(self->connection.net.fd); +} + +static char _mysql_ResultObject_num_fields__doc__[] = +"Returns the number of fields (column) in the result." ; + +static PyObject * +_mysql_ResultObject_num_fields( + _mysql_ResultObject *self, + PyObject *noargs) +{ + check_result_connection(self); + return PyLong_FromLong((long)mysql_num_fields(self->result)); +} + +static char _mysql_ResultObject_num_rows__doc__[] = +"Returns the number of rows in the result set. Note that if\n\ +use=1, this will not return a valid value until the entire result\n\ +set has been read.\n\ +"; + +static PyObject * +_mysql_ResultObject_num_rows( + _mysql_ResultObject *self, + PyObject *noargs) +{ + check_result_connection(self); + return PyLong_FromUnsignedLongLong(mysql_num_rows(self->result)); +} + +static char _mysql_ConnectionObject_ping__doc__[] = +"Checks whether or not the connection to the server is 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.\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\ +You have been warned.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_ping( + _mysql_ConnectionObject *self, + PyObject *args) +{ + int reconnect = 0; + if (!PyArg_ParseTuple(args, "|p", &reconnect)) return NULL; + check_connection(self); + 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); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_query__doc__[] = +"Execute a query. store_result() or use_result() will get the\n\ +result set, if any. Non-standard. Use cursor() to create a cursor,\n\ +then cursor.execute().\n\ +" ; + +static PyObject * +_mysql_ConnectionObject_query( + _mysql_ConnectionObject *self, + PyObject *args) +{ + char *query; + Py_ssize_t len; + int r; + if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; + check_connection(self); + + Py_BEGIN_ALLOW_THREADS + r = mysql_real_query(&(self->connection), query, len); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + + +static char _mysql_ConnectionObject_send_query__doc__[] = +"Send a query. Same to query() except not wait response.\n\n\ +Use read_query_result() before calling store_result() or use_result()\n"; + +static PyObject * +_mysql_ConnectionObject_send_query( + _mysql_ConnectionObject *self, + PyObject *args) +{ + char *query; + Py_ssize_t len; + int r; + MYSQL *mysql = &(self->connection); + if (!PyArg_ParseTuple(args, "s#:query", &query, &len)) return NULL; + check_connection(self); + + Py_BEGIN_ALLOW_THREADS + r = mysql_send_query(mysql, query, len); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + + +static char _mysql_ConnectionObject_read_query_result__doc__[] = +"Read result of query sent by send_query().\n"; + +static PyObject * +_mysql_ConnectionObject_read_query_result( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int r; + MYSQL *mysql = &(self->connection); + check_connection(self); + + Py_BEGIN_ALLOW_THREADS + r = (int)mysql_read_query_result(mysql); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_select_db__doc__[] = +"Causes the database specified by db to become the default\n\ +(current) database on the connection specified by mysql. In subsequent\n\ +queries, this database is the default for table references that do not\n\ +include an explicit database specifier.\n\ +\n\ +Fails unless the connected user can be authenticated as having\n\ +permission to use the database.\n\ +\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_select_db( + _mysql_ConnectionObject *self, + PyObject *args) +{ + char *db; + int r; + if (!PyArg_ParseTuple(args, "s:select_db", &db)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_select_db(&(self->connection), db); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_shutdown__doc__[] = +"Asks the database server to shut down. The connected user must\n\ +have shutdown privileges. Non-standard. Deprecated.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_shutdown( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + int r; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + r = mysql_query(&(self->connection), "SHUTDOWN"); + Py_END_ALLOW_THREADS + if (r) return _mysql_Exception(self); + Py_RETURN_NONE; +} + +static char _mysql_ConnectionObject_stat__doc__[] = +"Returns a character string containing information similar to\n\ +that provided by the mysqladmin status command. This includes\n\ +uptime in seconds and the number of running threads,\n\ +questions, reloads, and open tables. Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_stat( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + const char *s; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + s = mysql_stat(&(self->connection)); + Py_END_ALLOW_THREADS + if (!s) return _mysql_Exception(self); + return PyUnicode_FromString(s); +} + +static char _mysql_ConnectionObject_store_result__doc__[] = +"Returns a result object acquired by mysql_store_result\n\ +(results stored in the client). If no results are available,\n\ +None is returned. Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_store_result( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; + _mysql_ResultObject *r=NULL; + + check_connection(self); + arglist = Py_BuildValue("(OiO)", self, 0, self->converter); + if (!arglist) goto error; + kwarglist = PyDict_New(); + if (!kwarglist) goto error; + r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); + if (!r) goto error; + if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) { + Py_DECREF(r); + goto error; + } + result = (PyObject *) r; + if (!(r->result)) { + Py_DECREF(result); + Py_INCREF(Py_None); + result = Py_None; + } + error: + Py_XDECREF(arglist); + Py_XDECREF(kwarglist); + return result; +} + +static char _mysql_ConnectionObject_thread_id__doc__[] = +"Returns the thread ID of the current connection. This value\n\ +can be used as an argument to kill() to kill the thread.\n\ +\n\ +If the connection is lost and you reconnect with ping(), the\n\ +thread ID will change. This means you should not get the\n\ +thread ID and store it for later. You should get it when you\n\ +need it.\n\ +\n\ +Non-standard."; + +static PyObject * +_mysql_ConnectionObject_thread_id( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + unsigned long pid; + check_connection(self); + pid = mysql_thread_id(&(self->connection)); + return PyLong_FromLong((long)pid); +} + +static char _mysql_ConnectionObject_use_result__doc__[] = +"Returns a result object acquired by mysql_use_result\n\ +(results stored in the server). If no results are available,\n\ +None is returned. Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_use_result( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + PyObject *arglist=NULL, *kwarglist=NULL, *result=NULL; + _mysql_ResultObject *r=NULL; + + check_connection(self); + arglist = Py_BuildValue("(OiO)", self, 1, self->converter); + if (!arglist) return NULL; + kwarglist = PyDict_New(); + if (!kwarglist) goto error; + r = MyAlloc(_mysql_ResultObject, _mysql_ResultObject_Type); + if (!r) goto error; + if (_mysql_ResultObject_Initialize(r, arglist, kwarglist)) { + Py_DECREF(r); + goto error; + } + result = (PyObject *) r; + if (!(r->result)) { + Py_DECREF(result); + Py_INCREF(Py_None); + result = Py_None; + } + error: + Py_DECREF(arglist); + Py_XDECREF(kwarglist); + 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) +{ + PyObject_GC_UnTrack(self); + if (self->open) { + mysql_close(&(self->connection)); + self->open = false; + } + Py_CLEAR(self->converter); + MyFree(self); +} + +static PyObject * +_mysql_ConnectionObject_repr( + _mysql_ConnectionObject *self) +{ + char buf[300]; + if (self->open) + snprintf(buf, 300, "<_mysql.connection open to '%.256s' at %p>", + self->connection.host, self); + else + snprintf(buf, 300, "<_mysql.connection closed at %p>", self); + return PyUnicode_FromString(buf); +} + +static char _mysql_ResultObject_data_seek__doc__[] = +"data_seek(n) -- seek to row n of result set"; +static PyObject * +_mysql_ResultObject_data_seek( + _mysql_ResultObject *self, + PyObject *args) +{ + unsigned int row; + if (!PyArg_ParseTuple(args, "i:data_seek", &row)) return NULL; + check_result_connection(self); + mysql_data_seek(self->result, row); + Py_RETURN_NONE; +} + +static void +_mysql_ResultObject_dealloc( + _mysql_ResultObject *self) +{ + PyObject_GC_UnTrack((PyObject *)self); + mysql_free_result(self->result); + _mysql_ResultObject_clear(self); + MyFree(self); +} + +static PyObject * +_mysql_ResultObject_repr( + _mysql_ResultObject *self) +{ + char buf[300]; + snprintf(buf, 300, "<_mysql.result object at %p>", self); + return PyUnicode_FromString(buf); +} + +static PyMethodDef _mysql_ConnectionObject_methods[] = { + { + "affected_rows", + (PyCFunction)_mysql_ConnectionObject_affected_rows, + METH_NOARGS, + _mysql_ConnectionObject_affected_rows__doc__ + }, + { + "autocommit", + (PyCFunction)_mysql_ConnectionObject_autocommit, + METH_VARARGS, + _mysql_ConnectionObject_autocommit__doc__ + }, + { + "get_autocommit", + (PyCFunction)_mysql_ConnectionObject_get_autocommit, + METH_NOARGS, + _mysql_ConnectionObject_get_autocommit__doc__ + }, + { + "commit", + (PyCFunction)_mysql_ConnectionObject_commit, + METH_NOARGS, + _mysql_ConnectionObject_commit__doc__ + }, + { + "rollback", + (PyCFunction)_mysql_ConnectionObject_rollback, + METH_NOARGS, + _mysql_ConnectionObject_rollback__doc__ + }, + { + "next_result", + (PyCFunction)_mysql_ConnectionObject_next_result, + METH_NOARGS, + _mysql_ConnectionObject_next_result__doc__ + }, + { + "set_server_option", + (PyCFunction)_mysql_ConnectionObject_set_server_option, + METH_VARARGS, + _mysql_ConnectionObject_set_server_option__doc__ + }, + { + "sqlstate", + (PyCFunction)_mysql_ConnectionObject_sqlstate, + METH_NOARGS, + _mysql_ConnectionObject_sqlstate__doc__ + }, + { + "warning_count", + (PyCFunction)_mysql_ConnectionObject_warning_count, + METH_NOARGS, + _mysql_ConnectionObject_warning_count__doc__ + }, + { + "change_user", + (PyCFunction)_mysql_ConnectionObject_change_user, + METH_VARARGS | METH_KEYWORDS, + _mysql_ConnectionObject_change_user__doc__ + }, + { + "character_set_name", + (PyCFunction)_mysql_ConnectionObject_character_set_name, + METH_NOARGS, + _mysql_ConnectionObject_character_set_name__doc__ + }, + { + "set_character_set", + (PyCFunction)_mysql_ConnectionObject_set_character_set, + METH_VARARGS, + _mysql_ConnectionObject_set_character_set__doc__ + }, +#if MYSQL_VERSION_ID >= 50010 + { + "get_character_set_info", + (PyCFunction)_mysql_ConnectionObject_get_character_set_info, + METH_NOARGS, + _mysql_ConnectionObject_get_character_set_info__doc__ + }, +#endif + { + "_get_native_connection", + (PyCFunction)_mysql_ConnectionObject_get_native_connection, + METH_NOARGS, + _mysql_ConnectionObject_get_native_connection__doc__ + }, + { + "close", + (PyCFunction)_mysql_ConnectionObject_close, + METH_NOARGS, + _mysql_ConnectionObject_close__doc__ + }, + { + "dump_debug_info", + (PyCFunction)_mysql_ConnectionObject_dump_debug_info, + METH_NOARGS, + _mysql_ConnectionObject_dump_debug_info__doc__ + }, + { + "escape", + (PyCFunction)_mysql_escape, + METH_VARARGS, + _mysql_escape__doc__ + }, + { + "escape_string", + (PyCFunction)_mysql_escape_string, + METH_VARARGS, + _mysql_escape_string__doc__ + }, + { + "error", + (PyCFunction)_mysql_ConnectionObject_error, + METH_NOARGS, + _mysql_ConnectionObject_error__doc__ + }, + { + "errno", + (PyCFunction)_mysql_ConnectionObject_errno, + METH_NOARGS, + _mysql_ConnectionObject_errno__doc__ + }, + { + "field_count", + (PyCFunction)_mysql_ConnectionObject_field_count, + METH_NOARGS, + _mysql_ConnectionObject_field_count__doc__ + }, + { + "fileno", + (PyCFunction)_mysql_ConnectionObject_fileno, + METH_NOARGS, + _mysql_ConnectionObject_fileno__doc__ + }, + { + "get_host_info", + (PyCFunction)_mysql_ConnectionObject_get_host_info, + METH_NOARGS, + _mysql_ConnectionObject_get_host_info__doc__ + }, + { + "get_proto_info", + (PyCFunction)_mysql_ConnectionObject_get_proto_info, + METH_NOARGS, + _mysql_ConnectionObject_get_proto_info__doc__ + }, + { + "get_server_info", + (PyCFunction)_mysql_ConnectionObject_get_server_info, + METH_NOARGS, + _mysql_ConnectionObject_get_server_info__doc__ + }, + { + "info", + (PyCFunction)_mysql_ConnectionObject_info, + METH_NOARGS, + _mysql_ConnectionObject_info__doc__ + }, + { + "insert_id", + (PyCFunction)_mysql_ConnectionObject_insert_id, + METH_NOARGS, + _mysql_ConnectionObject_insert_id__doc__ + }, + { + "kill", + (PyCFunction)_mysql_ConnectionObject_kill, + METH_VARARGS, + _mysql_ConnectionObject_kill__doc__ + }, + { + "ping", + (PyCFunction)_mysql_ConnectionObject_ping, + METH_VARARGS, + _mysql_ConnectionObject_ping__doc__ + }, + { + "query", + (PyCFunction)_mysql_ConnectionObject_query, + METH_VARARGS, + _mysql_ConnectionObject_query__doc__ + }, + { + "send_query", + (PyCFunction)_mysql_ConnectionObject_send_query, + METH_VARARGS, + _mysql_ConnectionObject_send_query__doc__, + }, + { + "read_query_result", + (PyCFunction)_mysql_ConnectionObject_read_query_result, + METH_NOARGS, + _mysql_ConnectionObject_read_query_result__doc__, + }, + { + "select_db", + (PyCFunction)_mysql_ConnectionObject_select_db, + METH_VARARGS, + _mysql_ConnectionObject_select_db__doc__ + }, + { + "shutdown", + (PyCFunction)_mysql_ConnectionObject_shutdown, + METH_NOARGS, + _mysql_ConnectionObject_shutdown__doc__ + }, + { + "stat", + (PyCFunction)_mysql_ConnectionObject_stat, + METH_NOARGS, + _mysql_ConnectionObject_stat__doc__ + }, + { + "store_result", + (PyCFunction)_mysql_ConnectionObject_store_result, + METH_NOARGS, + _mysql_ConnectionObject_store_result__doc__ + }, + { + "string_literal", + (PyCFunction)_mysql_string_literal, + METH_O, + _mysql_string_literal__doc__}, + { + "thread_id", + (PyCFunction)_mysql_ConnectionObject_thread_id, + METH_NOARGS, + _mysql_ConnectionObject_thread_id__doc__ + }, + { + "use_result", + (PyCFunction)_mysql_ConnectionObject_use_result, + METH_NOARGS, + _mysql_ConnectionObject_use_result__doc__ + }, + { + "discard_result", + (PyCFunction)_mysql_ConnectionObject_discard_result, + METH_NOARGS, + _mysql_ConnectionObject_discard_result__doc__ + }, + {NULL, NULL} /* sentinel */ +}; + +static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { + { + "open", + T_INT, + offsetof(_mysql_ConnectionObject,open), + READONLY, + "True if connection is open" + }, + { + "converter", + T_OBJECT, + offsetof(_mysql_ConnectionObject,converter), + 0, + "Type conversion mapping" + }, + { + "server_capabilities", + T_ULONG, + offsetof(_mysql_ConnectionObject,connection.server_capabilities), + READONLY, + "Capabilities of server; consult MySQLdb.constants.CLIENT" + }, + { + "port", + T_UINT, + offsetof(_mysql_ConnectionObject,connection.port), + READONLY, + "TCP/IP port of the server connection" + }, + { + "client_flag", + T_ULONG, + offsetof(_mysql_ConnectionObject,connection.client_flag), + READONLY, + "Client flags; refer to MySQLdb.constants.CLIENT" + }, + {NULL} /* Sentinel */ +}; + +static PyMethodDef _mysql_ResultObject_methods[] = { + { + "data_seek", + (PyCFunction)_mysql_ResultObject_data_seek, + METH_VARARGS, + _mysql_ResultObject_data_seek__doc__ + }, + { + "describe", + (PyCFunction)_mysql_ResultObject_describe, + METH_NOARGS, + _mysql_ResultObject_describe__doc__ + }, + { + "fetch_row", + (PyCFunction)_mysql_ResultObject_fetch_row, + METH_VARARGS | METH_KEYWORDS, + _mysql_ResultObject_fetch_row__doc__ + }, + { + "discard", + (PyCFunction)_mysql_ResultObject_discard, + METH_NOARGS, + _mysql_ResultObject_discard__doc__ + }, + { + "field_flags", + (PyCFunction)_mysql_ResultObject_field_flags, + METH_NOARGS, + _mysql_ResultObject_field_flags__doc__ + }, + { + "num_fields", + (PyCFunction)_mysql_ResultObject_num_fields, + METH_NOARGS, + _mysql_ResultObject_num_fields__doc__ + }, + { + "num_rows", + (PyCFunction)_mysql_ResultObject_num_rows, + METH_NOARGS, + _mysql_ResultObject_num_rows__doc__ + }, + {NULL, NULL} /* sentinel */ +}; + +static struct PyMemberDef _mysql_ResultObject_memberlist[] = { + { + "converter", + T_OBJECT, + offsetof(_mysql_ResultObject,converter), + READONLY, + "Type conversion mapping" + }, + { + "has_next", + T_BOOL, + offsetof(_mysql_ResultObject, has_next), + READONLY, + "Has next result" + }, + {NULL} /* Sentinel */ +}; + +static PyObject * +_mysql_ConnectionObject_getattro( + _mysql_ConnectionObject *self, + PyObject *name) +{ + const char *cname; + cname = PyUnicode_AsUTF8(name); + if (strcmp(cname, "closed") == 0) + return PyLong_FromLong((long)!(self->open)); + + return PyObject_GenericGetAttr((PyObject *)self, name); +} + +static int +_mysql_ConnectionObject_setattro( + _mysql_ConnectionObject *self, + PyObject *name, + PyObject *v) +{ + if (v == NULL) { + PyErr_SetString(PyExc_AttributeError, + "can't delete connection attributes"); + return -1; + } + return PyObject_GenericSetAttr((PyObject *)self, name, v); +} + +static int +_mysql_ResultObject_setattro( + _mysql_ResultObject *self, + PyObject *name, + PyObject *v) +{ + if (v == NULL) { + PyErr_SetString(PyExc_AttributeError, + "can't delete connection attributes"); + return -1; + } + return PyObject_GenericSetAttr((PyObject *)self, name, v); +} + +PyTypeObject _mysql_ConnectionObject_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_mysql.connection", /* (char *)tp_name For printing */ + sizeof(_mysql_ConnectionObject), + 0, + (destructor)_mysql_ConnectionObject_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /*tp_compare*/ + (reprfunc)_mysql_ConnectionObject_repr, /* tp_repr */ + + /* Method suites for standard classes */ + + 0, /* (PyNumberMethods *) tp_as_number */ + 0, /* (PySequenceMethods *) tp_as_sequence */ + 0, /* (PyMappingMethods *) tp_as_mapping */ + + /* More standard operations (here for binary compatibility) */ + + 0, /* (hashfunc) tp_hash */ + 0, /* (ternaryfunc) tp_call */ + 0, /* (reprfunc) tp_str */ + (getattrofunc)_mysql_ConnectionObject_getattro, /* tp_getattro */ + (setattrofunc)_mysql_ConnectionObject_setattro, /* tp_setattro */ + + /* Functions to access object as input/output buffer */ + 0, /* (PyBufferProcs *) tp_as_buffer */ + + /* (tp_flags) Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, + _mysql_connect__doc__, /* (char *) tp_doc Documentation string */ + + /* call function for all accessible objects */ + (traverseproc) _mysql_ConnectionObject_traverse, /* tp_traverse */ + + /* delete references to contained objects */ + (inquiry) _mysql_ConnectionObject_clear, /* tp_clear */ + + /* rich comparisons */ + 0, /* (richcmpfunc) tp_richcompare */ + + /* weak reference enabler */ + 0, /* (long) tp_weaklistoffset */ + + /* Iterators */ + 0, /* (getiterfunc) tp_iter */ + 0, /* (iternextfunc) tp_iternext */ + + /* Attribute descriptor and subclassing stuff */ + (struct PyMethodDef *)_mysql_ConnectionObject_methods, /* tp_methods */ + (struct PyMemberDef *)_mysql_ConnectionObject_memberlist, /* tp_members */ + 0, /* (struct getsetlist *) tp_getset; */ + 0, /* (struct _typeobject *) tp_base; */ + 0, /* (PyObject *) tp_dict */ + 0, /* (descrgetfunc) tp_descr_get */ + 0, /* (descrsetfunc) tp_descr_set */ + 0, /* (long) tp_dictoffset */ + (initproc)_mysql_ConnectionObject_Initialize, /* tp_init */ + NULL, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ + NULL, /* tp_free Low-level free-memory routine */ + 0, /* (PyObject *) tp_bases */ + 0, /* (PyObject *) tp_mro method resolution order */ + 0, /* (PyObject *) tp_defined */ +} ; + +PyTypeObject _mysql_ResultObject_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_mysql.result", + sizeof(_mysql_ResultObject), + 0, + (destructor)_mysql_ResultObject_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /*tp_compare*/ + (reprfunc)_mysql_ResultObject_repr, /* tp_repr */ + + /* Method suites for standard classes */ + + 0, /* (PyNumberMethods *) tp_as_number */ + 0, /* (PySequenceMethods *) tp_as_sequence */ + 0, /* (PyMappingMethods *) tp_as_mapping */ + + /* More standard operations (here for binary compatibility) */ + + 0, /* (hashfunc) tp_hash */ + 0, /* (ternaryfunc) tp_call */ + 0, /* (reprfunc) tp_str */ + (getattrofunc)PyObject_GenericGetAttr, /* tp_getattro */ + (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ + + /* Functions to access object as input/output buffer */ + 0, /* (PyBufferProcs *) tp_as_buffer */ + + /* Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, + + _mysql_ResultObject__doc__, /* (char *) tp_doc Documentation string */ + + /* call function for all accessible objects */ + (traverseproc) _mysql_ResultObject_traverse, /* tp_traverse */ + + /* delete references to contained objects */ + (inquiry) _mysql_ResultObject_clear, /* tp_clear */ + + /* rich comparisons */ + 0, /* (richcmpfunc) tp_richcompare */ + + /* weak reference enabler */ + 0, /* (long) tp_weaklistoffset */ + + /* Iterators */ + 0, /* (getiterfunc) tp_iter */ + 0, /* (iternextfunc) tp_iternext */ + + /* Attribute descriptor and subclassing stuff */ + (struct PyMethodDef *) _mysql_ResultObject_methods, /* tp_methods */ + (struct PyMemberDef *) _mysql_ResultObject_memberlist, /*tp_members */ + 0, /* (struct getsetlist *) tp_getset; */ + 0, /* (struct _typeobject *) tp_base; */ + 0, /* (PyObject *) tp_dict */ + 0, /* (descrgetfunc) tp_descr_get */ + 0, /* (descrsetfunc) tp_descr_set */ + 0, /* (long) tp_dictoffset */ + (initproc)_mysql_ResultObject_Initialize, /* tp_init */ + NULL, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ + NULL, /* tp_free Low-level free-memory routine */ + 0, /* (PyObject *) tp_bases */ + 0, /* (PyObject *) tp_mro method resolution order */ + 0, /* (PyObject *) tp_defined */ +}; + +static PyMethodDef +_mysql_methods[] = { + { + "connect", + (PyCFunction)_mysql_connect, + METH_VARARGS | METH_KEYWORDS, + _mysql_connect__doc__ + }, + { + "debug", + (PyCFunction)_mysql_debug, + METH_VARARGS, + _mysql_debug__doc__ + }, + { + "escape", + (PyCFunction)_mysql_escape, + METH_VARARGS, + _mysql_escape__doc__ + }, + { + "escape_string", + (PyCFunction)_mysql_escape_string, + METH_VARARGS, + _mysql_escape_string__doc__ + }, + { + "string_literal", + (PyCFunction)_mysql_string_literal, + METH_O, + _mysql_string_literal__doc__ + }, + { + "get_client_info", + (PyCFunction)_mysql_get_client_info, + METH_NOARGS, + _mysql_get_client_info__doc__ + }, + {NULL, NULL} /* sentinel */ +}; + +static PyObject * +_mysql_NewException( + PyObject *dict, + PyObject *edict, + char *name) +{ + PyObject *e; + if (!(e = PyDict_GetItemString(edict, name))) + return NULL; + if (PyDict_SetItemString(dict, name, e)) + return NULL; + Py_INCREF(e); + return e; +} + +#define QUOTE(X) _QUOTE(X) +#define _QUOTE(X) #X + +static char _mysql___doc__[] = +"an adaptation of the MySQL C API (mostly)\n\ +\n\ +You probably are better off using MySQLdb instead of using this\n\ +module directly.\n\ +\n\ +In general, renaming goes from mysql_* to _mysql.*. _mysql.connect()\n\ +returns a connection object (MYSQL). Functions which expect MYSQL * as\n\ +an argument are now methods of the connection object. A number of things\n\ +return result objects (MYSQL_RES). Functions which expect MYSQL_RES * as\n\ +an argument are now methods of the result object. Deprecated functions\n\ +(as of 3.23) are NOT implemented.\n\ +"; + +static struct PyModuleDef _mysqlmodule = { + PyModuleDef_HEAD_INIT, + "_mysql", /* name of module */ + _mysql___doc__, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + _mysql_methods +}; + +PyMODINIT_FUNC +PyInit__mysql(void) +{ + PyObject *dict, *module, *emod, *edict; + + if (mysql_library_init(0, NULL, NULL)) { + PyErr_SetString(PyExc_ImportError, "_mysql: mysql_library_init failed"); + return NULL; + } + + if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) + return NULL; + if (PyType_Ready(&_mysql_ResultObject_Type) < 0) + return NULL; + + module = PyModule_Create(&_mysqlmodule); + if (!module) return module; /* this really should never happen */ + + if (!(dict = PyModule_GetDict(module))) goto error; + if (PyDict_SetItemString(dict, "version_info", + PyRun_String(QUOTE(version_info), Py_eval_input, + dict, dict))) + goto error; + if (PyDict_SetItemString(dict, "__version__", + PyUnicode_FromString(QUOTE(__version__)))) + goto error; + if (PyDict_SetItemString(dict, "connection", + (PyObject *)&_mysql_ConnectionObject_Type)) + goto error; + Py_INCREF(&_mysql_ConnectionObject_Type); + if (PyDict_SetItemString(dict, "result", + (PyObject *)&_mysql_ResultObject_Type)) + goto error; + Py_INCREF(&_mysql_ResultObject_Type); + if (!(emod = PyImport_ImportModule("MySQLdb._exceptions"))) { + PyErr_Print(); + goto error; + } + if (!(edict = PyModule_GetDict(emod))) goto error; + if (!(_mysql_MySQLError = + _mysql_NewException(dict, edict, "MySQLError"))) + goto error; + if (!(_mysql_Warning = + _mysql_NewException(dict, edict, "Warning"))) + goto error; + if (!(_mysql_Error = + _mysql_NewException(dict, edict, "Error"))) + goto error; + if (!(_mysql_InterfaceError = + _mysql_NewException(dict, edict, "InterfaceError"))) + goto error; + if (!(_mysql_DatabaseError = + _mysql_NewException(dict, edict, "DatabaseError"))) + goto error; + if (!(_mysql_DataError = + _mysql_NewException(dict, edict, "DataError"))) + goto error; + if (!(_mysql_OperationalError = + _mysql_NewException(dict, edict, "OperationalError"))) + goto error; + if (!(_mysql_IntegrityError = + _mysql_NewException(dict, edict, "IntegrityError"))) + goto error; + if (!(_mysql_InternalError = + _mysql_NewException(dict, edict, "InternalError"))) + goto error; + if (!(_mysql_ProgrammingError = + _mysql_NewException(dict, edict, "ProgrammingError"))) + goto error; + if (!(_mysql_NotSupportedError = + _mysql_NewException(dict, edict, "NotSupportedError"))) + goto error; + Py_DECREF(emod); + error: + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_ImportError, "_mysql: init failed"); + module = NULL; + } + return module; +} + +/* vim: set ts=4 sts=4 sw=4 expandtab : */ diff --git a/src/MySQLdb/connections.py b/src/MySQLdb/connections.py new file mode 100644 index 00000000..a61aaaed --- /dev/null +++ b/src/MySQLdb/connections.py @@ -0,0 +1,368 @@ +""" +This module implements connections for MySQLdb. Presently there is +only one class: Connection. Others are unlikely. However, you might +want to make your own subclasses. In most cases, you will probably +override Connection.default_cursor with a non-standard Cursor class. +""" +import re + +from . import cursors, _mysql +from ._exceptions import ( + Warning, + Error, + InterfaceError, + DataError, + DatabaseError, + OperationalError, + IntegrityError, + InternalError, + NotSupportedError, + ProgrammingError, +) + +# Mapping from MySQL charset name to Python codec name +_charset_to_encoding = { + "utf8mb4": "utf8", + "utf8mb3": "utf8", + "latin1": "cp1252", + "koi8r": "koi8_r", + "koi8u": "koi8_u", +} + +re_numeric_part = re.compile(r"^(\d+)") + + +def numeric_part(s): + """Returns the leading numeric part of a string. + + >>> numeric_part("20-alpha") + 20 + >>> numeric_part("foo") + >>> numeric_part("16b") + 16 + """ + + m = re_numeric_part.match(s) + if m: + return int(m.group(1)) + return None + + +class Connection(_mysql.connection): + """MySQL Database Connection Object""" + + default_cursor = cursors.Cursor + + def __init__(self, *args, **kwargs): + """ + Create a connection to the database. It is strongly recommended + that you only use keyword parameters. Consult the MySQL C API + documentation for more information. + + :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 (deprecated) + :param str database: database to use + :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 + :param int connect_timeout: + number of seconds to wait before the connection attempt fails. + + :param bool compress: if set, compression is enabled + :param str named_pipe: if set, a named pipe is used to connect (Windows only) + :param str init_command: + command which is run once the connection is created + + :param str read_default_file: + file from which default client values are read + + :param str read_default_group: + configuration group to use from the default file + + :param type cursorclass: + class object, used to create cursors (keyword only) + + :param bool use_unicode: + If True, text-like columns are returned as unicode objects + using the connection's character set. Otherwise, text-like + columns are returned as bytes. Unicode objects will always + be encoded to the connection's character set regardless of + this setting. + Default to True. + + :param str charset: + 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: + `mysql_native_password` or `caching_sha2_password` + + :param str sql_mode: + If supplied, the session SQL mode will be changed to this + setting. + For more details and legal values, see the MySQL documentation. + + :param int client_flag: + flags to use or 0 (see MySQL docs or constants/CLIENTS.py) + + :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 + (mysql_option(), MYSQL_OPT_SSL_MODE). + Only one of 'DISABLED', 'PREFERRED', 'REQUIRED', + 'VERIFY_CA', 'VERIFY_IDENTITY' can be specified. + + :param dict ssl: + dictionary or mapping contains SSL connection parameters; + see the MySQL documentation for more details + (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 str server_public_key_path: + specify the path to a file RSA public key file for caching_sha2_password. + See https://dev.mysql.com/doc/refman/9.0/en/caching-sha2-pluggable-authentication.html + + :param bool local_infile: + 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. + If True, autocommit is enabled. + If None, autocommit isn't set and server default is used. + + :param bool binary_prefix: + If set, the '_binary' prefix will be used for raw byte query + arguments (e.g. Binary). This is disabled by default. + + There are a number of undocumented, non-standard methods. See the + documentation for the MySQL C API for some hints on what they do. + """ + from MySQLdb.constants import CLIENT, FIELD_TYPE + from MySQLdb.converters import conversions, _bytes_or_str + + kwargs2 = kwargs.copy() + + 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"] + else: + conv = conversions + + conv2 = {} + for k, v in conv.items(): + if isinstance(k, int) and isinstance(v, list): + conv2[k] = v[:] + else: + conv2[k] = v + kwargs2["conv"] = conv2 + + 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) + + client_flag = kwargs.get("client_flag", 0) + client_flag |= CLIENT.MULTI_RESULTS + multi_statements = kwargs2.pop("multi_statements", True) + if multi_statements: + client_flag |= CLIENT.MULTI_STATEMENTS + kwargs2["client_flag"] = client_flag + + # 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._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: + charset = self.character_set_name() + self.set_character_set(charset, collation) + + if sql_mode: + self.set_sql_mode(sql_mode) + + if use_unicode: + for t in ( + FIELD_TYPE.STRING, + FIELD_TYPE.VAR_STRING, + FIELD_TYPE.VARCHAR, + FIELD_TYPE.TINY_BLOB, + FIELD_TYPE.MEDIUM_BLOB, + FIELD_TYPE.LONG_BLOB, + FIELD_TYPE.BLOB, + ): + self.converter[t] = _bytes_or_str + # Unlike other string/blob types, JSON is always text. + # MySQL may return JSON with charset==binary. + self.converter[FIELD_TYPE.JSON] = str + + self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS + if self._transactional: + if autocommit is not None: + 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 + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def autocommit(self, on): + on = bool(on) + if self.get_autocommit() != on: + _mysql.connection.autocommit(self, on) + + def cursor(self, cursorclass=None): + """ + Create a cursor on which queries may be performed. The + optional cursorclass parameter is used to create the + Cursor. By default, self.cursorclass=cursors.Cursor is + used. + """ + return (cursorclass or self.cursorclass)(self) + + def query(self, query): + # Since _mysql releases GIL while querying, we need immutable buffer. + if isinstance(query, bytearray): + query = bytes(query) + _mysql.connection.query(self, query) + + def _bytes_literal(self, bs): + assert isinstance(bs, (bytes, bytearray)) + x = self.string_literal(bs) # x is escaped and quoted bytes + if self._binary_prefix: + return b"_binary" + x + return x + + def _tuple_literal(self, t): + return b"(%s)" % (b",".join(map(self.literal, t))) + + def literal(self, o): + """If o is a single object, returns an SQL literal as a string. + If o is a non-string sequence, the items of the sequence are + converted and returned as a sequence. + + Non-standard. For internal use; do not use this in your + applications. + """ + if isinstance(o, str): + s = self.string_literal(o.encode(self.encoding)) + elif isinstance(o, bytearray): + s = self._bytes_literal(o) + elif isinstance(o, bytes): + s = self._bytes_literal(o) + elif isinstance(o, (tuple, list)): + s = self._tuple_literal(o) + else: + s = self.escape(o, self.encoders) + if isinstance(s, str): + s = s.encode(self.encoding) + assert isinstance(s, bytes) + return s + + def begin(self): + """Explicitly begin a connection. + + This method is not used when autocommit=False (default). + """ + self.query(b"BEGIN") + + 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(f"SET NAMES {charset} COLLATE {collation}") + self.store_result() + + def set_sql_mode(self, sql_mode): + """Set the connection sql_mode. See MySQL documentation for + legal values.""" + if self._server_version < (4, 1): + raise NotSupportedError("server is too old to set sql_mode") + self.query("SET SESSION sql_mode='%s'" % sql_mode) + self.store_result() + + def show_warnings(self): + """Return detailed information about warnings as a + sequence of tuples of (Level, Code, Message). This + is only supported in MySQL-4.1 and up. If your server + is an earlier version, an empty sequence is returned.""" + if self._server_version < (4, 1): + return () + self.query("SHOW WARNINGS") + r = self.store_result() + warnings = r.fetch_row(0) + return warnings + + Warning = Warning + Error = Error + InterfaceError = InterfaceError + DatabaseError = DatabaseError + DataError = DataError + OperationalError = OperationalError + IntegrityError = IntegrityError + InternalError = InternalError + ProgrammingError = ProgrammingError + NotSupportedError = NotSupportedError + + +# vim: colorcolumn=100 diff --git a/MySQLdb/constants/CLIENT.py b/src/MySQLdb/constants/CLIENT.py similarity index 90% rename from MySQLdb/constants/CLIENT.py rename to src/MySQLdb/constants/CLIENT.py index 6559917b..35f578cc 100644 --- a/MySQLdb/constants/CLIENT.py +++ b/src/MySQLdb/constants/CLIENT.py @@ -20,10 +20,8 @@ INTERACTIVE = 1024 SSL = 2048 IGNORE_SIGPIPE = 4096 -TRANSACTIONS = 8192 # mysql_com.h was WRONG prior to 3.23.35 +TRANSACTIONS = 8192 # mysql_com.h was WRONG prior to 3.23.35 RESERVED = 16384 SECURE_CONNECTION = 32768 MULTI_STATEMENTS = 65536 MULTI_RESULTS = 131072 - - diff --git a/src/MySQLdb/constants/CR.py b/src/MySQLdb/constants/CR.py new file mode 100644 index 00000000..9467ae11 --- /dev/null +++ b/src/MySQLdb/constants/CR.py @@ -0,0 +1,105 @@ +"""MySQL Connection Errors + +Nearly all of these raise OperationalError. COMMANDS_OUT_OF_SYNC +raises ProgrammingError. + +""" + +if __name__ == "__main__": + """ + Usage: python CR.py [/path/to/mysql/errmsg.h ...] >> CR.py + """ + import fileinput + import re + + data = {} + error_last = None + for line in fileinput.input(): + line = re.sub(r"/\*.*?\*/", "", line) + m = re.match(r"^\s*#define\s+CR_([A-Z0-9_]+)\s+(\d+)(\s.*|$)", line) + if m: + name = m.group(1) + value = int(m.group(2)) + if name == "ERROR_LAST": + if error_last is None or error_last < value: + error_last = value + continue + if value not in data: + data[value] = set() + data[value].add(name) + for value, names in sorted(data.items()): + for name in sorted(names): + print(f"{name} = {value}") + if error_last is not None: + print("ERROR_LAST = %s" % error_last) + + +ERROR_FIRST = 2000 +MIN_ERROR = 2000 +UNKNOWN_ERROR = 2000 +SOCKET_CREATE_ERROR = 2001 +CONNECTION_ERROR = 2002 +CONN_HOST_ERROR = 2003 +IPSOCK_ERROR = 2004 +UNKNOWN_HOST = 2005 +SERVER_GONE_ERROR = 2006 +VERSION_ERROR = 2007 +OUT_OF_MEMORY = 2008 +WRONG_HOST_INFO = 2009 +LOCALHOST_CONNECTION = 2010 +TCP_CONNECTION = 2011 +SERVER_HANDSHAKE_ERR = 2012 +SERVER_LOST = 2013 +COMMANDS_OUT_OF_SYNC = 2014 +NAMEDPIPE_CONNECTION = 2015 +NAMEDPIPEWAIT_ERROR = 2016 +NAMEDPIPEOPEN_ERROR = 2017 +NAMEDPIPESETSTATE_ERROR = 2018 +CANT_READ_CHARSET = 2019 +NET_PACKET_TOO_LARGE = 2020 +EMBEDDED_CONNECTION = 2021 +PROBE_SLAVE_STATUS = 2022 +PROBE_SLAVE_HOSTS = 2023 +PROBE_SLAVE_CONNECT = 2024 +PROBE_MASTER_CONNECT = 2025 +SSL_CONNECTION_ERROR = 2026 +MALFORMED_PACKET = 2027 +WRONG_LICENSE = 2028 +NULL_POINTER = 2029 +NO_PREPARE_STMT = 2030 +PARAMS_NOT_BOUND = 2031 +DATA_TRUNCATED = 2032 +NO_PARAMETERS_EXISTS = 2033 +INVALID_PARAMETER_NO = 2034 +INVALID_BUFFER_USE = 2035 +UNSUPPORTED_PARAM_TYPE = 2036 +SHARED_MEMORY_CONNECTION = 2037 +SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038 +SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039 +SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040 +SHARED_MEMORY_CONNECT_MAP_ERROR = 2041 +SHARED_MEMORY_FILE_MAP_ERROR = 2042 +SHARED_MEMORY_MAP_ERROR = 2043 +SHARED_MEMORY_EVENT_ERROR = 2044 +SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045 +SHARED_MEMORY_CONNECT_SET_ERROR = 2046 +CONN_UNKNOW_PROTOCOL = 2047 +INVALID_CONN_HANDLE = 2048 +UNUSED_1 = 2049 +FETCH_CANCELED = 2050 +NO_DATA = 2051 +NO_STMT_METADATA = 2052 +NO_RESULT_SET = 2053 +NOT_IMPLEMENTED = 2054 +SERVER_LOST_EXTENDED = 2055 +STMT_CLOSED = 2056 +NEW_STMT_METADATA = 2057 +ALREADY_CONNECTED = 2058 +AUTH_PLUGIN_CANNOT_LOAD = 2059 +DUPLICATE_CONNECTION_ATTR = 2060 +AUTH_PLUGIN_ERR = 2061 +INSECURE_API_ERR = 2062 +FILE_NAME_TOO_LONG = 2063 +SSL_FIPS_MODE_ERR = 2064 +MAX_ERROR = 2999 +ERROR_LAST = 2064 diff --git a/src/MySQLdb/constants/ER.py b/src/MySQLdb/constants/ER.py new file mode 100644 index 00000000..8c5ece24 --- /dev/null +++ b/src/MySQLdb/constants/ER.py @@ -0,0 +1,827 @@ +"""MySQL ER Constants + +These constants are error codes for the bulk of the error conditions +that may occur. +""" + +if __name__ == "__main__": + """ + Usage: python ER.py [/path/to/mysql/mysqld_error.h ...] >> ER.py + """ + import fileinput + import re + + data = {} + error_last = None + for line in fileinput.input(): + line = re.sub(r"/\*.*?\*/", "", line) + m = re.match(r"^\s*#define\s+((ER|WARN)_[A-Z0-9_]+)\s+(\d+)\s*", line) + if m: + name = m.group(1) + if name.startswith("ER_"): + name = name[3:] + value = int(m.group(3)) + if name == "ERROR_LAST": + if error_last is None or error_last < value: + error_last = value + continue + if value not in data: + data[value] = set() + data[value].add(name) + for value, names in sorted(data.items()): + for name in sorted(names): + print(f"{name} = {value}") + if error_last is not None: + print("ERROR_LAST = %s" % error_last) + + +ERROR_FIRST = 1000 +NO = 1002 +YES = 1003 +CANT_CREATE_FILE = 1004 +CANT_CREATE_TABLE = 1005 +CANT_CREATE_DB = 1006 +DB_CREATE_EXISTS = 1007 +DB_DROP_EXISTS = 1008 +DB_DROP_RMDIR = 1010 +CANT_FIND_SYSTEM_REC = 1012 +CANT_GET_STAT = 1013 +CANT_LOCK = 1015 +CANT_OPEN_FILE = 1016 +FILE_NOT_FOUND = 1017 +CANT_READ_DIR = 1018 +CHECKREAD = 1020 +DUP_KEY = 1022 +ERROR_ON_READ = 1024 +ERROR_ON_RENAME = 1025 +ERROR_ON_WRITE = 1026 +FILE_USED = 1027 +FILSORT_ABORT = 1028 +GET_ERRNO = 1030 +ILLEGAL_HA = 1031 +KEY_NOT_FOUND = 1032 +NOT_FORM_FILE = 1033 +NOT_KEYFILE = 1034 +OLD_KEYFILE = 1035 +OPEN_AS_READONLY = 1036 +OUTOFMEMORY = 1037 +OUT_OF_SORTMEMORY = 1038 +CON_COUNT_ERROR = 1040 +OUT_OF_RESOURCES = 1041 +BAD_HOST_ERROR = 1042 +HANDSHAKE_ERROR = 1043 +DBACCESS_DENIED_ERROR = 1044 +ACCESS_DENIED_ERROR = 1045 +NO_DB_ERROR = 1046 +UNKNOWN_COM_ERROR = 1047 +BAD_NULL_ERROR = 1048 +BAD_DB_ERROR = 1049 +TABLE_EXISTS_ERROR = 1050 +BAD_TABLE_ERROR = 1051 +NON_UNIQ_ERROR = 1052 +SERVER_SHUTDOWN = 1053 +BAD_FIELD_ERROR = 1054 +WRONG_FIELD_WITH_GROUP = 1055 +WRONG_GROUP_FIELD = 1056 +WRONG_SUM_SELECT = 1057 +WRONG_VALUE_COUNT = 1058 +TOO_LONG_IDENT = 1059 +DUP_FIELDNAME = 1060 +DUP_KEYNAME = 1061 +DUP_ENTRY = 1062 +WRONG_FIELD_SPEC = 1063 +PARSE_ERROR = 1064 +EMPTY_QUERY = 1065 +NONUNIQ_TABLE = 1066 +INVALID_DEFAULT = 1067 +MULTIPLE_PRI_KEY = 1068 +TOO_MANY_KEYS = 1069 +TOO_MANY_KEY_PARTS = 1070 +TOO_LONG_KEY = 1071 +KEY_COLUMN_DOES_NOT_EXITS = 1072 +BLOB_USED_AS_KEY = 1073 +TOO_BIG_FIELDLENGTH = 1074 +WRONG_AUTO_KEY = 1075 +READY = 1076 +SHUTDOWN_COMPLETE = 1079 +FORCING_CLOSE = 1080 +IPSOCK_ERROR = 1081 +NO_SUCH_INDEX = 1082 +WRONG_FIELD_TERMINATORS = 1083 +BLOBS_AND_NO_TERMINATED = 1084 +TEXTFILE_NOT_READABLE = 1085 +FILE_EXISTS_ERROR = 1086 +LOAD_INFO = 1087 +ALTER_INFO = 1088 +WRONG_SUB_KEY = 1089 +CANT_REMOVE_ALL_FIELDS = 1090 +CANT_DROP_FIELD_OR_KEY = 1091 +INSERT_INFO = 1092 +UPDATE_TABLE_USED = 1093 +NO_SUCH_THREAD = 1094 +KILL_DENIED_ERROR = 1095 +NO_TABLES_USED = 1096 +TOO_BIG_SET = 1097 +NO_UNIQUE_LOGFILE = 1098 +TABLE_NOT_LOCKED_FOR_WRITE = 1099 +TABLE_NOT_LOCKED = 1100 +BLOB_CANT_HAVE_DEFAULT = 1101 +WRONG_DB_NAME = 1102 +WRONG_TABLE_NAME = 1103 +TOO_BIG_SELECT = 1104 +UNKNOWN_ERROR = 1105 +UNKNOWN_PROCEDURE = 1106 +WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 +WRONG_PARAMETERS_TO_PROCEDURE = 1108 +UNKNOWN_TABLE = 1109 +FIELD_SPECIFIED_TWICE = 1110 +INVALID_GROUP_FUNC_USE = 1111 +UNSUPPORTED_EXTENSION = 1112 +TABLE_MUST_HAVE_COLUMNS = 1113 +RECORD_FILE_FULL = 1114 +UNKNOWN_CHARACTER_SET = 1115 +TOO_MANY_TABLES = 1116 +TOO_MANY_FIELDS = 1117 +TOO_BIG_ROWSIZE = 1118 +STACK_OVERRUN = 1119 +WRONG_OUTER_JOIN_UNUSED = 1120 +NULL_COLUMN_IN_INDEX = 1121 +CANT_FIND_UDF = 1122 +CANT_INITIALIZE_UDF = 1123 +UDF_NO_PATHS = 1124 +UDF_EXISTS = 1125 +CANT_OPEN_LIBRARY = 1126 +CANT_FIND_DL_ENTRY = 1127 +FUNCTION_NOT_DEFINED = 1128 +HOST_IS_BLOCKED = 1129 +HOST_NOT_PRIVILEGED = 1130 +PASSWORD_ANONYMOUS_USER = 1131 +PASSWORD_NOT_ALLOWED = 1132 +PASSWORD_NO_MATCH = 1133 +UPDATE_INFO = 1134 +CANT_CREATE_THREAD = 1135 +WRONG_VALUE_COUNT_ON_ROW = 1136 +CANT_REOPEN_TABLE = 1137 +INVALID_USE_OF_NULL = 1138 +REGEXP_ERROR = 1139 +MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 +NONEXISTING_GRANT = 1141 +TABLEACCESS_DENIED_ERROR = 1142 +COLUMNACCESS_DENIED_ERROR = 1143 +ILLEGAL_GRANT_FOR_TABLE = 1144 +GRANT_WRONG_HOST_OR_USER = 1145 +NO_SUCH_TABLE = 1146 +NONEXISTING_TABLE_GRANT = 1147 +NOT_ALLOWED_COMMAND = 1148 +SYNTAX_ERROR = 1149 +ABORTING_CONNECTION = 1152 +NET_PACKET_TOO_LARGE = 1153 +NET_READ_ERROR_FROM_PIPE = 1154 +NET_FCNTL_ERROR = 1155 +NET_PACKETS_OUT_OF_ORDER = 1156 +NET_UNCOMPRESS_ERROR = 1157 +NET_READ_ERROR = 1158 +NET_READ_INTERRUPTED = 1159 +NET_ERROR_ON_WRITE = 1160 +NET_WRITE_INTERRUPTED = 1161 +TOO_LONG_STRING = 1162 +TABLE_CANT_HANDLE_BLOB = 1163 +TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 +WRONG_COLUMN_NAME = 1166 +WRONG_KEY_COLUMN = 1167 +WRONG_MRG_TABLE = 1168 +DUP_UNIQUE = 1169 +BLOB_KEY_WITHOUT_LENGTH = 1170 +PRIMARY_CANT_HAVE_NULL = 1171 +TOO_MANY_ROWS = 1172 +REQUIRES_PRIMARY_KEY = 1173 +UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 +KEY_DOES_NOT_EXITS = 1176 +CHECK_NO_SUCH_TABLE = 1177 +CHECK_NOT_IMPLEMENTED = 1178 +CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 +ERROR_DURING_COMMIT = 1180 +ERROR_DURING_ROLLBACK = 1181 +ERROR_DURING_FLUSH_LOGS = 1182 +NEW_ABORTING_CONNECTION = 1184 +MASTER = 1188 +MASTER_NET_READ = 1189 +MASTER_NET_WRITE = 1190 +FT_MATCHING_KEY_NOT_FOUND = 1191 +LOCK_OR_ACTIVE_TRANSACTION = 1192 +UNKNOWN_SYSTEM_VARIABLE = 1193 +CRASHED_ON_USAGE = 1194 +CRASHED_ON_REPAIR = 1195 +WARNING_NOT_COMPLETE_ROLLBACK = 1196 +TRANS_CACHE_FULL = 1197 +SLAVE_NOT_RUNNING = 1199 +BAD_SLAVE = 1200 +MASTER_INFO = 1201 +SLAVE_THREAD = 1202 +TOO_MANY_USER_CONNECTIONS = 1203 +SET_CONSTANTS_ONLY = 1204 +LOCK_WAIT_TIMEOUT = 1205 +LOCK_TABLE_FULL = 1206 +READ_ONLY_TRANSACTION = 1207 +WRONG_ARGUMENTS = 1210 +NO_PERMISSION_TO_CREATE_USER = 1211 +LOCK_DEADLOCK = 1213 +TABLE_CANT_HANDLE_FT = 1214 +CANNOT_ADD_FOREIGN = 1215 +NO_REFERENCED_ROW = 1216 +ROW_IS_REFERENCED = 1217 +CONNECT_TO_MASTER = 1218 +ERROR_WHEN_EXECUTING_COMMAND = 1220 +WRONG_USAGE = 1221 +WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 +CANT_UPDATE_WITH_READLOCK = 1223 +MIXING_NOT_ALLOWED = 1224 +DUP_ARGUMENT = 1225 +USER_LIMIT_REACHED = 1226 +SPECIFIC_ACCESS_DENIED_ERROR = 1227 +LOCAL_VARIABLE = 1228 +GLOBAL_VARIABLE = 1229 +NO_DEFAULT = 1230 +WRONG_VALUE_FOR_VAR = 1231 +WRONG_TYPE_FOR_VAR = 1232 +VAR_CANT_BE_READ = 1233 +CANT_USE_OPTION_HERE = 1234 +NOT_SUPPORTED_YET = 1235 +MASTER_FATAL_ERROR_READING_BINLOG = 1236 +SLAVE_IGNORED_TABLE = 1237 +INCORRECT_GLOBAL_LOCAL_VAR = 1238 +WRONG_FK_DEF = 1239 +KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 +OPERAND_COLUMNS = 1241 +SUBQUERY_NO_1_ROW = 1242 +UNKNOWN_STMT_HANDLER = 1243 +CORRUPT_HELP_DB = 1244 +AUTO_CONVERT = 1246 +ILLEGAL_REFERENCE = 1247 +DERIVED_MUST_HAVE_ALIAS = 1248 +SELECT_REDUCED = 1249 +TABLENAME_NOT_ALLOWED_HERE = 1250 +NOT_SUPPORTED_AUTH_MODE = 1251 +SPATIAL_CANT_HAVE_NULL = 1252 +COLLATION_CHARSET_MISMATCH = 1253 +TOO_BIG_FOR_UNCOMPRESS = 1256 +ZLIB_Z_MEM_ERROR = 1257 +ZLIB_Z_BUF_ERROR = 1258 +ZLIB_Z_DATA_ERROR = 1259 +CUT_VALUE_GROUP_CONCAT = 1260 +WARN_TOO_FEW_RECORDS = 1261 +WARN_TOO_MANY_RECORDS = 1262 +WARN_NULL_TO_NOTNULL = 1263 +WARN_DATA_OUT_OF_RANGE = 1264 +WARN_DATA_TRUNCATED = 1265 +WARN_USING_OTHER_HANDLER = 1266 +CANT_AGGREGATE_2COLLATIONS = 1267 +REVOKE_GRANTS = 1269 +CANT_AGGREGATE_3COLLATIONS = 1270 +CANT_AGGREGATE_NCOLLATIONS = 1271 +VARIABLE_IS_NOT_STRUCT = 1272 +UNKNOWN_COLLATION = 1273 +SLAVE_IGNORED_SSL_PARAMS = 1274 +SERVER_IS_IN_SECURE_AUTH_MODE = 1275 +WARN_FIELD_RESOLVED = 1276 +BAD_SLAVE_UNTIL_COND = 1277 +MISSING_SKIP_SLAVE = 1278 +UNTIL_COND_IGNORED = 1279 +WRONG_NAME_FOR_INDEX = 1280 +WRONG_NAME_FOR_CATALOG = 1281 +BAD_FT_COLUMN = 1283 +UNKNOWN_KEY_CACHE = 1284 +WARN_HOSTNAME_WONT_WORK = 1285 +UNKNOWN_STORAGE_ENGINE = 1286 +WARN_DEPRECATED_SYNTAX = 1287 +NON_UPDATABLE_TABLE = 1288 +FEATURE_DISABLED = 1289 +OPTION_PREVENTS_STATEMENT = 1290 +DUPLICATED_VALUE_IN_TYPE = 1291 +TRUNCATED_WRONG_VALUE = 1292 +INVALID_ON_UPDATE = 1294 +UNSUPPORTED_PS = 1295 +GET_ERRMSG = 1296 +GET_TEMPORARY_ERRMSG = 1297 +UNKNOWN_TIME_ZONE = 1298 +WARN_INVALID_TIMESTAMP = 1299 +INVALID_CHARACTER_STRING = 1300 +WARN_ALLOWED_PACKET_OVERFLOWED = 1301 +CONFLICTING_DECLARATIONS = 1302 +SP_NO_RECURSIVE_CREATE = 1303 +SP_ALREADY_EXISTS = 1304 +SP_DOES_NOT_EXIST = 1305 +SP_DROP_FAILED = 1306 +SP_STORE_FAILED = 1307 +SP_LILABEL_MISMATCH = 1308 +SP_LABEL_REDEFINE = 1309 +SP_LABEL_MISMATCH = 1310 +SP_UNINIT_VAR = 1311 +SP_BADSELECT = 1312 +SP_BADRETURN = 1313 +SP_BADSTATEMENT = 1314 +UPDATE_LOG_DEPRECATED_IGNORED = 1315 +UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 +QUERY_INTERRUPTED = 1317 +SP_WRONG_NO_OF_ARGS = 1318 +SP_COND_MISMATCH = 1319 +SP_NORETURN = 1320 +SP_NORETURNEND = 1321 +SP_BAD_CURSOR_QUERY = 1322 +SP_BAD_CURSOR_SELECT = 1323 +SP_CURSOR_MISMATCH = 1324 +SP_CURSOR_ALREADY_OPEN = 1325 +SP_CURSOR_NOT_OPEN = 1326 +SP_UNDECLARED_VAR = 1327 +SP_WRONG_NO_OF_FETCH_ARGS = 1328 +SP_FETCH_NO_DATA = 1329 +SP_DUP_PARAM = 1330 +SP_DUP_VAR = 1331 +SP_DUP_COND = 1332 +SP_DUP_CURS = 1333 +SP_CANT_ALTER = 1334 +SP_SUBSELECT_NYI = 1335 +STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 +SP_VARCOND_AFTER_CURSHNDLR = 1337 +SP_CURSOR_AFTER_HANDLER = 1338 +SP_CASE_NOT_FOUND = 1339 +FPARSER_TOO_BIG_FILE = 1340 +FPARSER_BAD_HEADER = 1341 +FPARSER_EOF_IN_COMMENT = 1342 +FPARSER_ERROR_IN_PARAMETER = 1343 +FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 +VIEW_NO_EXPLAIN = 1345 +WRONG_OBJECT = 1347 +NONUPDATEABLE_COLUMN = 1348 +VIEW_SELECT_CLAUSE = 1350 +VIEW_SELECT_VARIABLE = 1351 +VIEW_SELECT_TMPTABLE = 1352 +VIEW_WRONG_LIST = 1353 +WARN_VIEW_MERGE = 1354 +WARN_VIEW_WITHOUT_KEY = 1355 +VIEW_INVALID = 1356 +SP_NO_DROP_SP = 1357 +TRG_ALREADY_EXISTS = 1359 +TRG_DOES_NOT_EXIST = 1360 +TRG_ON_VIEW_OR_TEMP_TABLE = 1361 +TRG_CANT_CHANGE_ROW = 1362 +TRG_NO_SUCH_ROW_IN_TRG = 1363 +NO_DEFAULT_FOR_FIELD = 1364 +DIVISION_BY_ZERO = 1365 +TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 +ILLEGAL_VALUE_FOR_TYPE = 1367 +VIEW_NONUPD_CHECK = 1368 +VIEW_CHECK_FAILED = 1369 +PROCACCESS_DENIED_ERROR = 1370 +RELAY_LOG_FAIL = 1371 +UNKNOWN_TARGET_BINLOG = 1373 +IO_ERR_LOG_INDEX_READ = 1374 +BINLOG_PURGE_PROHIBITED = 1375 +FSEEK_FAIL = 1376 +BINLOG_PURGE_FATAL_ERR = 1377 +LOG_IN_USE = 1378 +LOG_PURGE_UNKNOWN_ERR = 1379 +RELAY_LOG_INIT = 1380 +NO_BINARY_LOGGING = 1381 +RESERVED_SYNTAX = 1382 +PS_MANY_PARAM = 1390 +KEY_PART_0 = 1391 +VIEW_CHECKSUM = 1392 +VIEW_MULTIUPDATE = 1393 +VIEW_NO_INSERT_FIELD_LIST = 1394 +VIEW_DELETE_MERGE_VIEW = 1395 +CANNOT_USER = 1396 +XAER_NOTA = 1397 +XAER_INVAL = 1398 +XAER_RMFAIL = 1399 +XAER_OUTSIDE = 1400 +XAER_RMERR = 1401 +XA_RBROLLBACK = 1402 +NONEXISTING_PROC_GRANT = 1403 +PROC_AUTO_GRANT_FAIL = 1404 +PROC_AUTO_REVOKE_FAIL = 1405 +DATA_TOO_LONG = 1406 +SP_BAD_SQLSTATE = 1407 +STARTUP = 1408 +LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 +CANT_CREATE_USER_WITH_GRANT = 1410 +WRONG_VALUE_FOR_TYPE = 1411 +TABLE_DEF_CHANGED = 1412 +SP_DUP_HANDLER = 1413 +SP_NOT_VAR_ARG = 1414 +SP_NO_RETSET = 1415 +CANT_CREATE_GEOMETRY_OBJECT = 1416 +BINLOG_UNSAFE_ROUTINE = 1418 +BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 +STMT_HAS_NO_OPEN_CURSOR = 1421 +COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 +NO_DEFAULT_FOR_VIEW_FIELD = 1423 +SP_NO_RECURSION = 1424 +TOO_BIG_SCALE = 1425 +TOO_BIG_PRECISION = 1426 +M_BIGGER_THAN_D = 1427 +WRONG_LOCK_OF_SYSTEM_TABLE = 1428 +CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 +QUERY_ON_FOREIGN_DATA_SOURCE = 1430 +FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 +FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 +FOREIGN_DATA_STRING_INVALID = 1433 +TRG_IN_WRONG_SCHEMA = 1435 +STACK_OVERRUN_NEED_MORE = 1436 +TOO_LONG_BODY = 1437 +WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 +TOO_BIG_DISPLAYWIDTH = 1439 +XAER_DUPID = 1440 +DATETIME_FUNCTION_OVERFLOW = 1441 +CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 +VIEW_PREVENT_UPDATE = 1443 +PS_NO_RECURSION = 1444 +SP_CANT_SET_AUTOCOMMIT = 1445 +VIEW_FRM_NO_USER = 1447 +VIEW_OTHER_USER = 1448 +NO_SUCH_USER = 1449 +FORBID_SCHEMA_CHANGE = 1450 +ROW_IS_REFERENCED_2 = 1451 +NO_REFERENCED_ROW_2 = 1452 +SP_BAD_VAR_SHADOW = 1453 +TRG_NO_DEFINER = 1454 +OLD_FILE_FORMAT = 1455 +SP_RECURSION_LIMIT = 1456 +SP_WRONG_NAME = 1458 +TABLE_NEEDS_UPGRADE = 1459 +SP_NO_AGGREGATE = 1460 +MAX_PREPARED_STMT_COUNT_REACHED = 1461 +VIEW_RECURSIVE = 1462 +NON_GROUPING_FIELD_USED = 1463 +TABLE_CANT_HANDLE_SPKEYS = 1464 +NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 +REMOVED_SPACES = 1466 +AUTOINC_READ_FAILED = 1467 +USERNAME = 1468 +HOSTNAME = 1469 +WRONG_STRING_LENGTH = 1470 +NON_INSERTABLE_TABLE = 1471 +ADMIN_WRONG_MRG_TABLE = 1472 +TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 +NAME_BECOMES_EMPTY = 1474 +AMBIGUOUS_FIELD_TERM = 1475 +FOREIGN_SERVER_EXISTS = 1476 +FOREIGN_SERVER_DOESNT_EXIST = 1477 +ILLEGAL_HA_CREATE_OPTION = 1478 +PARTITION_REQUIRES_VALUES_ERROR = 1479 +PARTITION_WRONG_VALUES_ERROR = 1480 +PARTITION_MAXVALUE_ERROR = 1481 +PARTITION_WRONG_NO_PART_ERROR = 1484 +PARTITION_WRONG_NO_SUBPART_ERROR = 1485 +WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 +FIELD_NOT_FOUND_PART_ERROR = 1488 +INCONSISTENT_PARTITION_INFO_ERROR = 1490 +PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 +PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 +RANGE_NOT_INCREASING_ERROR = 1493 +INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 +MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 +PARTITION_ENTRY_ERROR = 1496 +MIX_HANDLER_ERROR = 1497 +PARTITION_NOT_DEFINED_ERROR = 1498 +TOO_MANY_PARTITIONS_ERROR = 1499 +SUBPARTITION_ERROR = 1500 +CANT_CREATE_HANDLER_FILE = 1501 +BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 +UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 +NO_PARTS_ERROR = 1504 +PARTITION_MGMT_ON_NONPARTITIONED = 1505 +FOREIGN_KEY_ON_PARTITIONED = 1506 +DROP_PARTITION_NON_EXISTENT = 1507 +DROP_LAST_PARTITION = 1508 +COALESCE_ONLY_ON_HASH_PARTITION = 1509 +REORG_HASH_ONLY_ON_SAME_NO = 1510 +REORG_NO_PARAM_ERROR = 1511 +ONLY_ON_RANGE_LIST_PARTITION = 1512 +ADD_PARTITION_SUBPART_ERROR = 1513 +ADD_PARTITION_NO_NEW_PARTITION = 1514 +COALESCE_PARTITION_NO_PARTITION = 1515 +REORG_PARTITION_NOT_EXIST = 1516 +SAME_NAME_PARTITION = 1517 +NO_BINLOG_ERROR = 1518 +CONSECUTIVE_REORG_PARTITIONS = 1519 +REORG_OUTSIDE_RANGE = 1520 +PARTITION_FUNCTION_FAILURE = 1521 +LIMITED_PART_RANGE = 1523 +PLUGIN_IS_NOT_LOADED = 1524 +WRONG_VALUE = 1525 +NO_PARTITION_FOR_GIVEN_VALUE = 1526 +FILEGROUP_OPTION_ONLY_ONCE = 1527 +CREATE_FILEGROUP_FAILED = 1528 +DROP_FILEGROUP_FAILED = 1529 +TABLESPACE_AUTO_EXTEND_ERROR = 1530 +WRONG_SIZE_NUMBER = 1531 +SIZE_OVERFLOW_ERROR = 1532 +ALTER_FILEGROUP_FAILED = 1533 +BINLOG_ROW_LOGGING_FAILED = 1534 +EVENT_ALREADY_EXISTS = 1537 +EVENT_DOES_NOT_EXIST = 1539 +EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 +EVENT_ENDS_BEFORE_STARTS = 1543 +EVENT_EXEC_TIME_IN_THE_PAST = 1544 +EVENT_SAME_NAME = 1551 +DROP_INDEX_FK = 1553 +WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 +CANT_LOCK_LOG_TABLE = 1556 +FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 +COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 +TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 +STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 +PARTITION_NO_TEMPORARY = 1562 +PARTITION_CONST_DOMAIN_ERROR = 1563 +PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 +NULL_IN_VALUES_LESS_THAN = 1566 +WRONG_PARTITION_NAME = 1567 +CANT_CHANGE_TX_CHARACTERISTICS = 1568 +DUP_ENTRY_AUTOINCREMENT_CASE = 1569 +EVENT_SET_VAR_ERROR = 1571 +PARTITION_MERGE_ERROR = 1572 +BASE64_DECODE_ERROR = 1575 +EVENT_RECURSION_FORBIDDEN = 1576 +ONLY_INTEGERS_ALLOWED = 1578 +UNSUPORTED_LOG_ENGINE = 1579 +BAD_LOG_STATEMENT = 1580 +CANT_RENAME_LOG_TABLE = 1581 +WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 +WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 +WRONG_PARAMETERS_TO_STORED_FCT = 1584 +NATIVE_FCT_NAME_COLLISION = 1585 +DUP_ENTRY_WITH_KEY_NAME = 1586 +BINLOG_PURGE_EMFILE = 1587 +EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 +EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 +NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 +BINLOG_UNSAFE_STATEMENT = 1592 +BINLOG_FATAL_ERROR = 1593 +BINLOG_LOGGING_IMPOSSIBLE = 1598 +VIEW_NO_CREATION_CTX = 1599 +VIEW_INVALID_CREATION_CTX = 1600 +TRG_CORRUPTED_FILE = 1602 +TRG_NO_CREATION_CTX = 1603 +TRG_INVALID_CREATION_CTX = 1604 +EVENT_INVALID_CREATION_CTX = 1605 +TRG_CANT_OPEN_TABLE = 1606 +NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 +SLAVE_CORRUPT_EVENT = 1610 +LOG_PURGE_NO_FILE = 1612 +XA_RBTIMEOUT = 1613 +XA_RBDEADLOCK = 1614 +NEED_REPREPARE = 1615 +WARN_NO_MASTER_INFO = 1617 +WARN_OPTION_IGNORED = 1618 +PLUGIN_DELETE_BUILTIN = 1619 +WARN_PLUGIN_BUSY = 1620 +VARIABLE_IS_READONLY = 1621 +WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 +SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 +NDB_REPLICATION_SCHEMA_ERROR = 1625 +CONFLICT_FN_PARSE_ERROR = 1626 +EXCEPTIONS_WRITE_ERROR = 1627 +TOO_LONG_TABLE_COMMENT = 1628 +TOO_LONG_FIELD_COMMENT = 1629 +FUNC_INEXISTENT_NAME_COLLISION = 1630 +DATABASE_NAME = 1631 +TABLE_NAME = 1632 +PARTITION_NAME = 1633 +SUBPARTITION_NAME = 1634 +TEMPORARY_NAME = 1635 +RENAMED_NAME = 1636 +TOO_MANY_CONCURRENT_TRXS = 1637 +WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 +DEBUG_SYNC_TIMEOUT = 1639 +DEBUG_SYNC_HIT_LIMIT = 1640 +DUP_SIGNAL_SET = 1641 +SIGNAL_WARN = 1642 +SIGNAL_NOT_FOUND = 1643 +SIGNAL_EXCEPTION = 1644 +RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 +SIGNAL_BAD_CONDITION_TYPE = 1646 +WARN_COND_ITEM_TRUNCATED = 1647 +COND_ITEM_TOO_LONG = 1648 +UNKNOWN_LOCALE = 1649 +SLAVE_IGNORE_SERVER_IDS = 1650 +SAME_NAME_PARTITION_FIELD = 1652 +PARTITION_COLUMN_LIST_ERROR = 1653 +WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 +TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 +MAXVALUE_IN_VALUES_IN = 1656 +TOO_MANY_VALUES_ERROR = 1657 +ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 +FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 +PARTITION_FIELDS_TOO_LONG = 1660 +BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 +BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 +BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 +BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 +BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 +BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 +BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 +BINLOG_UNSAFE_LIMIT = 1668 +BINLOG_UNSAFE_SYSTEM_TABLE = 1670 +BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 +BINLOG_UNSAFE_UDF = 1672 +BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 +BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 +BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 +MESSAGE_AND_STATEMENT = 1676 +SLAVE_CANT_CREATE_CONVERSION = 1678 +INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 +PATH_LENGTH = 1680 +WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 +WRONG_NATIVE_TABLE_STRUCTURE = 1682 +WRONG_PERFSCHEMA_USAGE = 1683 +WARN_I_S_SKIPPED_TABLE = 1684 +INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 +STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 +SPATIAL_MUST_HAVE_GEOM_COL = 1687 +TOO_LONG_INDEX_COMMENT = 1688 +LOCK_ABORTED = 1689 +DATA_OUT_OF_RANGE = 1690 +WRONG_SPVAR_TYPE_IN_LIMIT = 1691 +BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 +BINLOG_UNSAFE_MIXED_STATEMENT = 1693 +INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 +STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 +FAILED_READ_FROM_PAR_FILE = 1696 +VALUES_IS_NOT_INT_TYPE_ERROR = 1697 +ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 +SET_PASSWORD_AUTH_PLUGIN = 1699 +TRUNCATE_ILLEGAL_FK = 1701 +PLUGIN_IS_PERMANENT = 1702 +SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 +SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 +STMT_CACHE_FULL = 1705 +MULTI_UPDATE_KEY_CONFLICT = 1706 +TABLE_NEEDS_REBUILD = 1707 +WARN_OPTION_BELOW_LIMIT = 1708 +INDEX_COLUMN_TOO_LONG = 1709 +ERROR_IN_TRIGGER_BODY = 1710 +ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 +INDEX_CORRUPT = 1712 +UNDO_RECORD_TOO_BIG = 1713 +BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 +BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 +BINLOG_UNSAFE_REPLACE_SELECT = 1716 +BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 +BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 +BINLOG_UNSAFE_UPDATE_IGNORE = 1719 +PLUGIN_NO_UNINSTALL = 1720 +PLUGIN_NO_INSTALL = 1721 +BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 +BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 +BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 +TABLE_IN_FK_CHECK = 1725 +UNSUPPORTED_ENGINE = 1726 +BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 +CANNOT_LOAD_FROM_TABLE_V2 = 1728 +MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 +ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 +PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 +PARTITION_EXCHANGE_PART_TABLE = 1732 +PARTITION_EXCHANGE_TEMP_TABLE = 1733 +PARTITION_INSTEAD_OF_SUBPARTITION = 1734 +UNKNOWN_PARTITION = 1735 +TABLES_DIFFERENT_METADATA = 1736 +ROW_DOES_NOT_MATCH_PARTITION = 1737 +BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 +WARN_INDEX_NOT_APPLICABLE = 1739 +PARTITION_EXCHANGE_FOREIGN_KEY = 1740 +RPL_INFO_DATA_TOO_LONG = 1742 +BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 +CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 +PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 +ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 +CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 +WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 +WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 +MTS_FEATURE_IS_NOT_SUPPORTED = 1753 +MTS_UPDATED_DBS_GREATER_MAX = 1754 +MTS_CANT_PARALLEL = 1755 +MTS_INCONSISTENT_DATA = 1756 +FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 +DA_INVALID_CONDITION_NUMBER = 1758 +INSECURE_PLAIN_TEXT = 1759 +INSECURE_CHANGE_MASTER = 1760 +FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 +FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 +SQLTHREAD_WITH_SECURE_SLAVE = 1763 +TABLE_HAS_NO_FT = 1764 +VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 +VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 +SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 +GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 +MALFORMED_GTID_SET_SPECIFICATION = 1772 +MALFORMED_GTID_SET_ENCODING = 1773 +MALFORMED_GTID_SPECIFICATION = 1774 +GNO_EXHAUSTED = 1775 +BAD_SLAVE_AUTO_POSITION = 1776 +AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777 +CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 +GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 +CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 +CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 +CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 +GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 +GTID_UNSAFE_CREATE_SELECT = 1786 +GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787 +GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 +MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 +CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 +UNKNOWN_EXPLAIN_FORMAT = 1791 +CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 +TOO_LONG_TABLE_PARTITION_COMMENT = 1793 +SLAVE_CONFIGURATION = 1794 +INNODB_FT_LIMIT = 1795 +INNODB_NO_FT_TEMP_TABLE = 1796 +INNODB_FT_WRONG_DOCID_COLUMN = 1797 +INNODB_FT_WRONG_DOCID_INDEX = 1798 +INNODB_ONLINE_LOG_TOO_BIG = 1799 +UNKNOWN_ALTER_ALGORITHM = 1800 +UNKNOWN_ALTER_LOCK = 1801 +MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 +MTS_RECOVERY_FAILURE = 1803 +MTS_RESET_WORKERS = 1804 +COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 +SLAVE_SILENT_RETRY_TRANSACTION = 1806 +DISCARD_FK_CHECKS_RUNNING = 1807 +TABLE_SCHEMA_MISMATCH = 1808 +TABLE_IN_SYSTEM_TABLESPACE = 1809 +IO_READ_ERROR = 1810 +IO_WRITE_ERROR = 1811 +TABLESPACE_MISSING = 1812 +TABLESPACE_EXISTS = 1813 +TABLESPACE_DISCARDED = 1814 +INTERNAL_ERROR = 1815 +INNODB_IMPORT_ERROR = 1816 +INNODB_INDEX_CORRUPT = 1817 +INVALID_YEAR_COLUMN_LENGTH = 1818 +NOT_VALID_PASSWORD = 1819 +MUST_CHANGE_PASSWORD = 1820 +FK_NO_INDEX_CHILD = 1821 +FK_NO_INDEX_PARENT = 1822 +FK_FAIL_ADD_SYSTEM = 1823 +FK_CANNOT_OPEN_PARENT = 1824 +FK_INCORRECT_OPTION = 1825 +FK_DUP_NAME = 1826 +PASSWORD_FORMAT = 1827 +FK_COLUMN_CANNOT_DROP = 1828 +FK_COLUMN_CANNOT_DROP_CHILD = 1829 +FK_COLUMN_NOT_NULL = 1830 +DUP_INDEX = 1831 +FK_COLUMN_CANNOT_CHANGE = 1832 +FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 +MALFORMED_PACKET = 1835 +READ_ONLY_MODE = 1836 +GTID_NEXT_TYPE_UNDEFINED_GTID = 1837 +VARIABLE_NOT_SETTABLE_IN_SP = 1838 +CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 +CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 +GTID_PURGED_WAS_CHANGED = 1842 +GTID_EXECUTED_WAS_CHANGED = 1843 +BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 +ALTER_OPERATION_NOT_SUPPORTED = 1845 +ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 +ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 +ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 +ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 +ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 +ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 +ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 +ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 +ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 +ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 +ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 +SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 +DUP_UNKNOWN_IN_INDEX = 1859 +IDENT_CAUSES_TOO_LONG_PATH = 1860 +ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 +MUST_CHANGE_PASSWORD_LOGIN = 1862 +ROW_IN_WRONG_PARTITION = 1863 +MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 +BINLOG_LOGICAL_CORRUPTION = 1866 +WARN_PURGE_LOG_IN_USE = 1867 +WARN_PURGE_LOG_IS_ACTIVE = 1868 +AUTO_INCREMENT_CONFLICT = 1869 +WARN_ON_BLOCKHOLE_IN_RBR = 1870 +SLAVE_MI_INIT_REPOSITORY = 1871 +SLAVE_RLI_INIT_REPOSITORY = 1872 +ACCESS_DENIED_CHANGE_USER_ERROR = 1873 +INNODB_READ_ONLY = 1874 +STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875 +STOP_SLAVE_IO_THREAD_TIMEOUT = 1876 +TABLE_CORRUPT = 1877 +TEMP_FILE_WRITE_FAILURE = 1878 +INNODB_FT_AUX_NOT_HEX_ID = 1879 +OLD_TEMPORALS_UPGRADED = 1880 +INNODB_FORCED_RECOVERY = 1881 +AES_INVALID_IV = 1882 +PLUGIN_CANNOT_BE_UNINSTALLED = 1883 +GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884 +SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885 +MISSING_KEY = 1886 +ERROR_LAST = 1973 diff --git a/MySQLdb/constants/FIELD_TYPE.py b/src/MySQLdb/constants/FIELD_TYPE.py similarity index 80% rename from MySQLdb/constants/FIELD_TYPE.py rename to src/MySQLdb/constants/FIELD_TYPE.py index 8a57b171..3c4eca9f 100644 --- a/MySQLdb/constants/FIELD_TYPE.py +++ b/src/MySQLdb/constants/FIELD_TYPE.py @@ -2,7 +2,6 @@ These constants represent the various column (field) types that are supported by MySQL. - """ DECIMAL = 0 @@ -19,9 +18,13 @@ TIME = 11 DATETIME = 12 YEAR = 13 -NEWDATE = 14 +# NEWDATE = 14 # Internal to MySQL. VARCHAR = 15 BIT = 16 +# TIMESTAMP2 = 17 +# DATETIME2 = 18 +# TIME2 = 19 +JSON = 245 NEWDECIMAL = 246 ENUM = 247 SET = 248 @@ -34,4 +37,4 @@ GEOMETRY = 255 CHAR = TINY -INTERVAL = ENUM +INTERVAL = ENUM 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/src/MySQLdb/constants/__init__.py b/src/MySQLdb/constants/__init__.py new file mode 100644 index 00000000..0372265b --- /dev/null +++ b/src/MySQLdb/constants/__init__.py @@ -0,0 +1 @@ +__all__ = ["CR", "FIELD_TYPE", "CLIENT", "ER", "FLAG"] diff --git a/MySQLdb/converters.py b/src/MySQLdb/converters.py similarity index 57% rename from MySQLdb/converters.py rename to src/MySQLdb/converters.py index 8a9908d4..d6fdc01c 100644 --- a/MySQLdb/converters.py +++ b/src/MySQLdb/converters.py @@ -29,117 +29,111 @@ Don't modify conversions if you can avoid it. Instead, make copies (with the copy() method), modify the copies, and then pass them to MySQL.connect(). - """ +from decimal import Decimal -from _mysql import string_literal, escape_sequence, escape_dict, escape, NULL +from MySQLdb._mysql import string_literal from MySQLdb.constants import FIELD_TYPE, FLAG -from MySQLdb.times import * -from MySQLdb.compat import PY2, long - -NoneType = type(None) +from MySQLdb.times import ( + Date, + DateTimeType, + DateTime2literal, + DateTimeDeltaType, + DateTimeDelta2literal, + DateTime_or_None, + TimeDelta_or_None, + Date_or_None, +) +from MySQLdb._exceptions import ProgrammingError import array +NoneType = type(None) + try: ArrayType = array.ArrayType except AttributeError: ArrayType = array.array -def Bool2Str(s, d): return str(int(s)) +def Bool2Str(s, d): + return b"1" if s else b"0" -def Str2Set(s): - return set([ i for i in s.split(',') if i ]) def Set2Str(s, d): - return string_literal(','.join(s), d) + # Only support ascii string. Not tested. + return string_literal(",".join(s)) + def Thing2Str(s, d): """Convert something into a string via str().""" return str(s) -def Unicode2Str(s, d): - """Convert a unicode object to a string using the default encoding. - This is only used as a placeholder for the real function, which - is connection-dependent.""" - return s.encode() def Float2Str(o, d): - return '%.15g' % o + s = repr(o) + if s in ("inf", "-inf", "nan"): + raise ProgrammingError("%s can not be used with MySQL" % s) + if "e" not in s: + s += "e0" + return s + def None2NULL(o, d): """Convert None to NULL.""" - return NULL # duh + return b"NULL" + def Thing2Literal(o, d): """Convert something into a SQL string literal. If using MySQL-3.23 or newer, string_literal() is a method of the _mysql.MYSQL object, and this function will be overridden with that method when the connection is created.""" - return string_literal(o, d) + return string_literal(o) + +def Decimal2Literal(o, d): + return format(o, "f") -def char_array(s): - return array.array('c', s) def array2Str(o, d): return Thing2Literal(o.tostring(), d) -def quote_tuple(t, d): - return "(%s)" % (','.join(escape_sequence(t, d))) # bytes or str regarding to BINARY_FLAG. -_bytes_or_str = [(FLAG.BINARY, bytes)] +_bytes_or_str = ((FLAG.BINARY, bytes), (None, str)) conversions = { int: Thing2Str, - long: Thing2Str, float: Float2Str, NoneType: None2NULL, - tuple: quote_tuple, - list: quote_tuple, - dict: escape_dict, ArrayType: array2Str, bool: Bool2Str, Date: Thing2Literal, DateTimeType: DateTime2literal, DateTimeDeltaType: DateTimeDelta2literal, - str: Thing2Literal, # default set: Set2Str, + Decimal: Decimal2Literal, FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, - FIELD_TYPE.LONG: long, + FIELD_TYPE.LONG: int, FIELD_TYPE.FLOAT: float, FIELD_TYPE.DOUBLE: float, - FIELD_TYPE.DECIMAL: float, - FIELD_TYPE.NEWDECIMAL: float, - FIELD_TYPE.LONGLONG: long, + FIELD_TYPE.DECIMAL: Decimal, + FIELD_TYPE.NEWDECIMAL: Decimal, + FIELD_TYPE.LONGLONG: int, FIELD_TYPE.INT24: int, FIELD_TYPE.YEAR: int, - FIELD_TYPE.SET: Str2Set, - FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter, + FIELD_TYPE.TIMESTAMP: DateTime_or_None, FIELD_TYPE.DATETIME: DateTime_or_None, FIELD_TYPE.TIME: TimeDelta_or_None, FIELD_TYPE.DATE: Date_or_None, - - FIELD_TYPE.TINY_BLOB: _bytes_or_str, - FIELD_TYPE.MEDIUM_BLOB: _bytes_or_str, - FIELD_TYPE.LONG_BLOB: _bytes_or_str, - FIELD_TYPE.BLOB: _bytes_or_str, - FIELD_TYPE.STRING: _bytes_or_str, - FIELD_TYPE.VAR_STRING: _bytes_or_str, - FIELD_TYPE.VARCHAR: _bytes_or_str, + FIELD_TYPE.TINY_BLOB: bytes, + FIELD_TYPE.MEDIUM_BLOB: bytes, + FIELD_TYPE.LONG_BLOB: bytes, + FIELD_TYPE.BLOB: bytes, + FIELD_TYPE.STRING: bytes, + FIELD_TYPE.VAR_STRING: bytes, + FIELD_TYPE.VARCHAR: bytes, + FIELD_TYPE.JSON: bytes, } - -if PY2: - conversions[unicode] = Unicode2Str -else: - conversions[bytes] = Thing2Literal - -try: - from decimal import Decimal - conversions[FIELD_TYPE.DECIMAL] = Decimal - conversions[FIELD_TYPE.NEWDECIMAL] = Decimal -except ImportError: - pass diff --git a/MySQLdb/cursors.py b/src/MySQLdb/cursors.py similarity index 51% rename from MySQLdb/cursors.py rename to src/MySQLdb/cursors.py index 3e4a0e7b..70fbeea4 100644 --- a/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -3,36 +3,27 @@ This module implements Cursors of various types for MySQLdb. By default, MySQLdb uses the Cursor class. """ -from __future__ import print_function, absolute_import -from functools import partial import re -import sys -from MySQLdb.compat import unicode -from _mysql_exceptions import ( - Warning, Error, InterfaceError, DataError, - DatabaseError, OperationalError, IntegrityError, InternalError, - NotSupportedError, ProgrammingError) +from ._exceptions import ProgrammingError -PY2 = sys.version_info[0] == 2 -if PY2: - text_type = unicode -else: - text_type = str - - -#: Regular expression for :meth:`Cursor.executemany`. -#: executemany only suports simple bulk insert. +#: Regular expression for ``Cursor.executemany```. +#: executemany only supports simple bulk insert. #: You can use it to load large dataset. RE_INSERT_VALUES = re.compile( - r"\s*((?:INSERT|REPLACE)\s.+\sVALUES?\s+)" + - r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" + - r"(\s*(?:ON DUPLICATE.*)?)\Z", - re.IGNORECASE | re.DOTALL) - - -class BaseCursor(object): + "".join( + [ + r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)", + r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))", + r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z", + ] + ), + re.IGNORECASE | re.DOTALL, +) + + +class BaseCursor: """A base for Cursor classes. Useful attributes: description @@ -49,44 +40,69 @@ class BaseCursor(object): default number of rows fetchmany() will fetch """ - #: Max stetement size which :meth:`executemany` generates. + #: Max statement size which :meth:`executemany` generates. #: #: Max size of allowed statement is max_allowed_packet - packet_header_size. #: Default value of max_allowed_packet is 1048576. - max_stmt_length = 64*1024 + max_stmt_length = 64 * 1024 + + from ._exceptions import ( + MySQLError, + Warning, + Error, + InterfaceError, + DatabaseError, + DataError, + OperationalError, + IntegrityError, + InternalError, + ProgrammingError, + NotSupportedError, + ) - from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \ - DatabaseError, DataError, OperationalError, IntegrityError, \ - InternalError, ProgrammingError, NotSupportedError - - _defer_warnings = False connection = None 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 + self.lastrowid = None - self.messages = [] - self.errorhandler = connection.errorhandler self._result = None - self._warnings = 0 - self._info = None self.rownumber = None + self._rows = None + + def _discard(self): + self.description = None + self.description_flags = 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 + + 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.errorhandler = None self._result = None def __enter__(self): @@ -96,61 +112,9 @@ def __exit__(self, *exc_info): del exc_info self.close() - def _ensure_bytes(self, x, encoding=None): - if isinstance(x, text_type): - x = x.encode(encoding) - elif isinstance(x, (tuple, list)): - x = type(x)(self._ensure_bytes(v, encoding=encoding) for v in x) - return x - - def _escape_args(self, args, conn): - ensure_bytes = partial(self._ensure_bytes, encoding=conn.encoding) - - if isinstance(args, (tuple, list)): - if PY2: - args = tuple(map(ensure_bytes, args)) - return tuple(conn.literal(arg) for arg in args) - elif isinstance(args, dict): - if PY2: - args = dict((ensure_bytes(key), ensure_bytes(val)) for - (key, val) in args.items()) - return dict((key, conn.literal(val)) for (key, val) in args.items()) - else: - # If it's not a dictionary let's try escaping it anyways. - # Worst case it will throw a Value error - if PY2: - args = ensure_bytes(args) - return conn.literal(args) - def _check_executed(self): if not self._executed: - self.errorhandler(self, ProgrammingError, "execute() first") - - def _warning_check(self): - from warnings import warn - if self._warnings: - # When there is next result, fetching warnings cause "command - # out of sync" error. - if self._result and self._result.has_next: - msg = "There are %d MySQL warnings." % (self._warnings,) - self.messages.append(msg) - warn(msg, self.Warning, 3) - return - - warnings = self._get_db().show_warnings() - if warnings: - # This is done in two loops in case - # Warnings are set to raise exceptions. - for w in warnings: - self.messages.append((self.Warning, w)) - for w in warnings: - msg = w[-1] - if not PY2 and isinstance(msg, bytes): - msg = msg.decode() - warn(msg, self.Warning, 3) - elif self._info: - self.messages.append((self.Warning, self._info)) - warn(self._info, self.Warning, 3) + raise ProgrammingError("execute() first") def nextset(self): """Advance to the next result set. @@ -159,29 +123,29 @@ def nextset(self): """ if self._executed: self.fetchall() - del self.messages[:] db = self._get_db() nr = db.next_result() if nr == -1: return None - self._do_get_result() + self._do_get_result(db) self._post_get_result() - self._warning_check() return 1 - def _post_get_result(self): pass + def _do_get_result(self, db): + self._result = result = self._get_result() + if result is None: + self.description = self.description_flags = None + else: + self.description = result.describe() + self.description_flags = result.field_flags() - def _do_get_result(self): - db = self._get_db() - self._result = self._get_result() self.rowcount = db.affected_rows() self.rownumber = 0 - self.description = self._result and self._result.describe() or None - self.description_flags = self._result and self._result.field_flags() or None self.lastrowid = db.insert_id() - self._warnings = db.warning_count() - self._info = db.info() + + def _post_get_result(self): + pass def setinputsizes(self, *args): """Does nothing, required by DB API.""" @@ -207,45 +171,51 @@ def execute(self, query, args=None): Returns integer represents rows affected, if any """ - while self.nextset(): - pass - db = self._get_db() + self._discard() - # NOTE: - # Python 2: query should be bytes when executing %. - # All unicode in args should be encoded to bytes on Python 2. - # Python 3: query should be str (unicode) when executing %. - # All bytes in args should be decoded with ascii and surrogateescape on Python 3. - # db.literal(obj) always returns str. + mogrified_query = self._mogrify(query, args) - if PY2 and isinstance(query, unicode): - query = query.encode(db.unicode_literal.charset) + 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): + query = query.encode(db.encoding) if args is not None: if isinstance(args, dict): - args = dict((key, db.literal(item)) for key, item in args.items()) + nargs = {} + for key, item in args.items(): + if isinstance(key, str): + key = key.encode(db.encoding) + nargs[key] = db.literal(item) + args = nargs else: args = tuple(map(db.literal, args)) - if not PY2 and isinstance(query, (bytes, bytearray)): - query = query.decode(db.unicode_literal.charset) try: query = query % args except TypeError as m: - self.errorhandler(self, ProgrammingError, str(m)) + raise ProgrammingError(str(m)) - if isinstance(query, unicode): - query = query.encode(db.unicode_literal.charset, 'surrogateescape') + return query - res = None - try: - res = self._query(query) - except Exception: - exc, value = sys.exc_info()[:2] - self.errorhandler(self, exc, value) - self._executed = query - if not self._defer_warnings: - self._warning_check() - return res + 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 @@ -259,8 +229,6 @@ def executemany(self, query, args): REPLACE. Otherwise it is equivalent to looping over args with execute(). """ - del self.messages[:] - if not args: return @@ -268,46 +236,41 @@ def executemany(self, query, args): if m: q_prefix = m.group(1) % () q_values = m.group(2).rstrip() - q_postfix = m.group(3) or '' - assert q_values[0] == '(' and q_values[-1] == ')' - return self._do_execute_many(q_prefix, q_values, q_postfix, args, - self.max_stmt_length, - self._get_db().encoding) + q_postfix = m.group(3) or "" + assert q_values[0] == "(" and q_values[-1] == ")" + return self._do_execute_many( + q_prefix, + q_values, + q_postfix, + args, + self.max_stmt_length, + self._get_db().encoding, + ) self.rowcount = sum(self.execute(query, arg) for arg in args) return self.rowcount - def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding): - conn = self._get_db() - escape = self._escape_args - if isinstance(prefix, text_type): + def _do_execute_many( + self, prefix, values, postfix, args, max_stmt_length, encoding + ): + if isinstance(prefix, str): prefix = prefix.encode(encoding) - if PY2 and isinstance(values, text_type): + if isinstance(values, str): values = values.encode(encoding) - if isinstance(postfix, text_type): + if isinstance(postfix, str): postfix = postfix.encode(encoding) sql = bytearray(prefix) args = iter(args) - v = values % escape(next(args), conn) - if isinstance(v, text_type): - if PY2: - v = v.encode(encoding) - else: - v = v.encode(encoding, 'surrogateescape') + v = self._mogrify(values, next(args)) sql += v rows = 0 for arg in args: - v = values % escape(arg, conn) - if isinstance(v, text_type): - if PY2: - v = v.encode(encoding) - else: - v = v.encode(encoding, 'surrogateescape') + v = self._mogrify(values, arg) if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length: rows += self.execute(sql + postfix) sql = bytearray(prefix) else: - sql += b',' + sql += b"," sql += v rows += self.execute(sql + postfix) self.rowcount = rows @@ -341,37 +304,35 @@ def callproc(self, procname, args=()): to advance through all result sets; otherwise you may get disconnected. """ - db = self._get_db() - for index, arg in enumerate(args): - q = "SET @_%s_%d=%s" % (procname, index, - db.literal(arg)) - if isinstance(q, unicode): - q = q.encode(db.unicode_literal.charset) + if isinstance(procname, str): + procname = procname.encode(db.encoding) + if args: + fmt = b"@_" + procname + b"_%d=%s" + q = b"SET %s" % b",".join( + fmt % (index, db.literal(arg)) for index, arg in enumerate(args) + ) self._query(q) self.nextset() - q = "CALL %s(%s)" % (procname, - ','.join(['@_%s_%d' % (procname, i) - for i in range(len(args))])) - if isinstance(q, unicode): - q = q.encode(db.unicode_literal.charset, 'surrogateescape') + q = b"CALL %s(%s)" % ( + procname, + b",".join([b"@_%s_%d" % (procname, i) for i in range(len(args))]), + ) self._query(q) - self._executed = q - if not self._defer_warnings: - self._warning_check() return args - def _do_query(self, q): + def _query(self, q): db = self._get_db() - self._last_executed = q + self._result = None + self.rowcount = None + self.lastrowid = None db.query(q) - self._do_get_result() + self._do_get_result(db) + self._post_get_result() + self._executed = q return self.rowcount - def _query(self, q): - return self._do_query(q) - def _fetch_row(self, size=1): if not self._result: return () @@ -392,7 +353,7 @@ def __iter__(self): NotSupportedError = NotSupportedError -class CursorStoreResultMixIn(object): +class CursorStoreResultMixIn: """This is a MixIn class which causes the entire result set to be stored on the client side, i.e. it uses mysql_store_result(). If the result set can be very large, consider adding a LIMIT clause to your @@ -401,11 +362,6 @@ class CursorStoreResultMixIn(object): def _get_result(self): return self._get_db().store_result() - def _query(self, q): - rowcount = self._do_query(q) - self._post_get_result() - return rowcount - def _post_get_result(self): self._rows = self._fetch_row(0) self._result = None @@ -425,21 +381,21 @@ def fetchmany(self, size=None): than size. If size is not defined, cursor.arraysize is used.""" self._check_executed() end = self.rownumber + (size or self.arraysize) - result = self._rows[self.rownumber:end] + result = self._rows[self.rownumber : end] self.rownumber = min(end, len(self._rows)) return result 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:] + result = self._rows[self.rownumber :] else: result = self._rows self.rownumber = len(self._rows) return result - def scroll(self, value, mode='relative'): + def scroll(self, value, mode="relative"): """Scroll the cursor in the result set to a new position according to mode. @@ -447,41 +403,38 @@ def scroll(self, value, mode='relative'): the current position in the result set, if set to 'absolute', value states an absolute target position.""" self._check_executed() - if mode == 'relative': + if mode == "relative": r = self.rownumber + value - elif mode == 'absolute': + elif mode == "absolute": r = value else: - self.errorhandler(self, ProgrammingError, - "unknown scroll mode %s" % repr(mode)) + raise ProgrammingError("unknown scroll mode %s" % repr(mode)) if r < 0 or r >= len(self._rows): - self.errorhandler(self, IndexError, "out of range") + raise IndexError("out of range") self.rownumber = r def __iter__(self): self._check_executed() - result = self.rownumber and self._rows[self.rownumber:] or self._rows + result = self.rownumber and self._rows[self.rownumber :] or self._rows return iter(result) -class CursorUseResultMixIn(object): +class CursorUseResultMixIn: """This is a MixIn class which causes the result set to be stored in the server and sent row-by-row to client side, i.e. it uses mysql_use_result(). You MUST retrieve the entire result set and - close() the cursor before additional queries can be peformed on + close() the cursor before additional queries can be performed on the connection.""" - _defer_warnings = True - - def _get_result(self): return self._get_db().use_result() + def _get_result(self): + return self._get_db().use_result() def fetchone(self): """Fetches a single row from the cursor.""" self._check_executed() r = self._fetch_row(1) if not r: - self._warning_check() return None self.rownumber = self.rownumber + 1 return r[0] @@ -492,16 +445,13 @@ def fetchmany(self, size=None): self._check_executed() r = self._fetch_row(size or self.arraysize) self.rownumber = self.rownumber + len(r) - if not r: - self._warning_check() return r def fetchall(self): - """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) - self._warning_check() return r def __iter__(self): @@ -516,73 +466,35 @@ def next(self): __next__ = next -class CursorTupleRowsMixIn(object): +class CursorTupleRowsMixIn: """This is a MixIn class that causes all rows to be returned as tuples, which is the standard form required by DB API.""" _fetch_type = 0 -class CursorDictRowsMixIn(object): +class CursorDictRowsMixIn: """This is a MixIn class that causes all rows to be returned as dictionaries. This is a non-standard feature.""" _fetch_type = 1 - def fetchoneDict(self): - """Fetch a single row as a dictionary. Deprecated: - Use fetchone() instead. Will be removed in 1.3.""" - from warnings import warn - warn("fetchoneDict() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - return self.fetchone() - - def fetchmanyDict(self, size=None): - """Fetch several rows as a list of dictionaries. Deprecated: - Use fetchmany() instead. Will be removed in 1.3.""" - from warnings import warn - warn("fetchmanyDict() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - return self.fetchmany(size) - - def fetchallDict(self): - """Fetch all available rows as a list of dictionaries. Deprecated: - Use fetchall() instead. Will be removed in 1.3.""" - from warnings import warn - warn("fetchallDict() is non-standard and will be removed in 1.3", - DeprecationWarning, 2) - return self.fetchall() - - -class CursorOldDictRowsMixIn(CursorDictRowsMixIn): - """This is a MixIn class that returns rows as dictionaries with - the same key convention as the old Mysqldb (MySQLmodule). Don't - use this.""" - - _fetch_type = 2 - - -class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, - BaseCursor): + +class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn, BaseCursor): """This is the standard Cursor class that returns rows as tuples and stores the result set in the client.""" -class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, - BaseCursor): - """This is a Cursor class that returns rows as dictionaries and +class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn, BaseCursor): + """This is a Cursor class that returns rows as dictionaries and stores the result set in the client.""" -class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn, - BaseCursor): +class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn, BaseCursor): """This is a Cursor class that returns rows as tuples and stores the result set in the server.""" -class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, - BaseCursor): +class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn, BaseCursor): """This is a Cursor class that returns rows as dictionaries and stores the result set in the server.""" - - diff --git a/src/MySQLdb/release.py b/src/MySQLdb/release.py new file mode 100644 index 00000000..234d9958 --- /dev/null +++ b/src/MySQLdb/release.py @@ -0,0 +1,3 @@ +__author__ = "Inada Naoki " +__version__ = "2.2.7" +version_info = (2, 2, 7, "final", 0) diff --git a/MySQLdb/times.py b/src/MySQLdb/times.py similarity index 60% rename from MySQLdb/times.py rename to src/MySQLdb/times.py index e26f8868..915d827b 100644 --- a/MySQLdb/times.py +++ b/src/MySQLdb/times.py @@ -6,7 +6,7 @@ """ from time import localtime from datetime import date, datetime, time, timedelta -from _mysql import string_literal +from MySQLdb._mysql import string_literal Date = date Time = time @@ -16,34 +16,50 @@ DateTimeDeltaType = timedelta DateTimeType = datetime + def DateFromTicks(ticks): """Convert UNIX ticks into a date instance.""" return date(*localtime(ticks)[:3]) + def TimeFromTicks(ticks): """Convert UNIX ticks into a time instance.""" return time(*localtime(ticks)[3:6]) + def TimestampFromTicks(ticks): """Convert UNIX ticks into a datetime instance.""" return datetime(*localtime(ticks)[:6]) + format_TIME = format_DATE = str + def format_TIMEDELTA(v): seconds = int(v.seconds) % 60 minutes = int(v.seconds // 60) % 60 hours = int(v.seconds // 3600) % 24 - return '%d %d:%d:%d' % (v.days, hours, minutes, seconds) + return "%d %d:%d:%d" % (v.days, hours, minutes, seconds) + def format_TIMESTAMP(d): """ :type d: datetime.datetime """ if d.microsecond: - fmt = "{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}" + fmt = " ".join( + [ + "{0.year:04}-{0.month:02}-{0.day:02}", + "{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}", + ] + ) else: - fmt = "{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}" + fmt = " ".join( + [ + "{0.year:04}-{0.month:02}-{0.day:02}", + "{0.hour:02}:{0.minute:02}:{0.second:02}", + ] + ) return fmt.format(d) @@ -64,32 +80,32 @@ def DateTime_or_None(s): return None return datetime( - int(s[:4]), # year - int(s[5:7]), # month - int(s[8:10]), # day + int(s[:4]), # year + int(s[5:7]), # month + int(s[8:10]), # day int(s[11:13] or 0), # hour int(s[14:16] or 0), # minute int(s[17:19] or 0), # second - micros, # microsecond + micros, # microsecond ) except ValueError: return None + def TimeDelta_or_None(s): try: - h, m, s = s.split(':') - if '.' in s: - s, ms = s.split('.') - ms = ms.ljust(6, '0') + h, m, s = s.split(":") + if "." in s: + s, ms = s.split(".") + ms = ms.ljust(6, "0") else: ms = 0 - if h[0] == '-': + if h[0] == "-": negative = True else: negative = False h, m, s, ms = abs(int(h)), int(m), int(s), int(ms) - td = timedelta(hours=h, minutes=m, seconds=s, - microseconds=ms) + td = timedelta(hours=h, minutes=m, seconds=s, microseconds=ms) if negative: return -td else: @@ -98,48 +114,37 @@ def TimeDelta_or_None(s): # unpacking or int/float conversion failed return None + def Time_or_None(s): try: - h, m, s = s.split(':') - if '.' in s: - s, ms = s.split('.') - ms = ms.ljust(6, '0') + h, m, s = s.split(":") + if "." in s: + s, ms = s.split(".") + ms = ms.ljust(6, "0") else: ms = 0 h, m, s, ms = int(h), int(m), int(s), int(ms) - return time(hour=h, minute=m, second=s, - microsecond=ms) + return time(hour=h, minute=m, second=s, microsecond=ms) except ValueError: return None + def Date_or_None(s): try: return date( - int(s[:4]), # year - int(s[5:7]), # month - int(s[8:10]), # day - ) + int(s[:4]), + int(s[5:7]), + int(s[8:10]), + ) # year # month # day except ValueError: return None + def DateTime2literal(d, c): """Format a DateTime object as an ISO timestamp.""" - return string_literal(format_TIMESTAMP(d), c) + return string_literal(format_TIMESTAMP(d)) + def DateTimeDelta2literal(d, c): """Format a DateTimeDelta object as a time.""" - return string_literal(format_TIMEDELTA(d),c) - -def mysql_timestamp_converter(s): - """Convert a MySQL TIMESTAMP to a Timestamp object.""" - # MySQL>4.1 returns TIMESTAMP in the same format as DATETIME - if s[4] == '-': return DateTime_or_None(s) - s = s + "0"*(14-len(s)) # padding - parts = map(int, filter(None, (s[:4],s[4:6],s[6:8], - s[8:10],s[10:12],s[12:14]))) - try: - return Timestamp(*parts) - except (SystemExit, KeyboardInterrupt): - raise # pragma: no cover - except: - return None + return string_literal(format_TIMEDELTA(d)) diff --git a/tests/actions.cnf b/tests/actions.cnf new file mode 100644 index 00000000..d20296d6 --- /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 = root +default-character-set = utf8mb4 diff --git a/tests/capabilities.py b/tests/capabilities.py index 31aa398e..1e695e9e 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -1,281 +1,314 @@ -#!/usr/bin/env python -O -""" Script to test database capabilities and the DB-API interface - for functionality and memory leaks. - - Adapted from a script by M-A Lemburg. - -""" -from time import time -import array -import unittest -from configdb import connection_factory - -from MySQLdb.compat import unichr - - -class DatabaseTest(unittest.TestCase): - - db_module = None - connect_args = () - connect_kwargs = dict() - create_table_extra = '' - rows = 10 - debug = False - - def setUp(self): - import gc - db = connection_factory(**self.connect_kwargs) - self.connection = db - self.cursor = db.cursor() - self.BLOBUText = u''.join([unichr(i) for i in range(16384)]) - self.BLOBBinary = self.db_module.Binary((u''.join([unichr(i) for i in range(256)] * 16)).encode('latin1')) - - leak_test = True - - def tearDown(self): - if self.leak_test: - import gc - del self.cursor - orphans = gc.collect() - self.failIf(orphans, "%d orphaned objects found after deleting cursor" % orphans) - - del self.connection - orphans = gc.collect() - self.failIf(orphans, "%d orphaned objects found after deleting connection" % orphans) - - def table_exists(self, name): - try: - self.cursor.execute('select * from %s where 1=0' % name) - except: - return False - else: - return True - - def quote_identifier(self, ident): - return '"%s"' % ident - - def new_table_name(self): - i = id(self.cursor) - while True: - name = self.quote_identifier('tb%08x' % i) - if not self.table_exists(name): - return name - i = i + 1 - - def create_table(self, columndefs): - - """ Create a table using a list of column definitions given in - columndefs. - - generator must be a function taking arguments (row_number, - col_number) returning a suitable data object for insertion - into the table. - - """ - self.table = self.new_table_name() - self.cursor.execute('CREATE TABLE %s (%s) %s' % - (self.table, - ',\n'.join(columndefs), - self.create_table_extra)) - - def check_data_integrity(self, columndefs, generator): - # insert - self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] - self.cursor.executemany(insert_statement, data) - self.connection.commit() - # verify - self.cursor.execute('select * from %s' % self.table) - l = self.cursor.fetchall() - self.assertEqual(len(l), self.rows) - try: - for i in range(self.rows): - for j in range(len(columndefs)): - self.assertEqual(l[i][j], generator(i,j)) - finally: - if not self.debug: - self.cursor.execute('drop table %s' % (self.table)) - - def test_transactions(self): - columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') - def generator(row, col): - if col == 0: return row - else: return ('%i' % (row%10))*255 - self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] - self.cursor.executemany(insert_statement, data) - # verify - self.connection.commit() - self.cursor.execute('select * from %s' % self.table) - l = self.cursor.fetchall() - self.assertEqual(len(l), self.rows) - for i in range(self.rows): - for j in range(len(columndefs)): - self.assertEqual(l[i][j], generator(i,j)) - delete_statement = 'delete from %s where col1=%%s' % self.table - self.cursor.execute(delete_statement, (0,)) - self.cursor.execute('select col1 from %s where col1=%s' % \ - (self.table, 0)) - l = self.cursor.fetchall() - self.assertFalse(l, "DELETE didn't work") - self.connection.rollback() - self.cursor.execute('select col1 from %s where col1=%s' % \ - (self.table, 0)) - l = self.cursor.fetchall() - self.assertTrue(len(l) == 1, "ROLLBACK didn't work") - self.cursor.execute('drop table %s' % (self.table)) - - def test_truncation(self): - columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') - def generator(row, col): - if col == 0: return row - else: return ('%i' % (row%10))*((255-self.rows//2)+row) - self.create_table(columndefs) - insert_statement = ('INSERT INTO %s VALUES (%s)' % - (self.table, - ','.join(['%s'] * len(columndefs)))) - - try: - self.cursor.execute(insert_statement, (0, '0'*256)) - except self.connection.DataError: - pass - else: - self.fail("Over-long column did not generate warnings/exception with single insert") - - self.connection.rollback() - - try: - for i in range(self.rows): - data = [] - for j in range(len(columndefs)): - data.append(generator(i,j)) - self.cursor.execute(insert_statement,tuple(data)) - except self.connection.DataError: - pass - else: - self.fail("Over-long columns did not generate warnings/exception with execute()") - - self.connection.rollback() - - try: - data = [ [ generator(i,j) for j in range(len(columndefs)) ] - for i in range(self.rows) ] - self.cursor.executemany(insert_statement, data) - except self.connection.DataError: - pass - else: - self.fail("Over-long columns did not generate warnings/exception with executemany()") - - self.connection.rollback() - self.cursor.execute('drop table %s' % (self.table)) - - def test_CHAR(self): - # Character data - def generator(row,col): - return ('%i' % ((row+col) % 10)) * 255 - self.check_data_integrity( - ('col1 char(255)','col2 char(255)'), - generator) - - def test_INT(self): - # Number data - def generator(row,col): - return row*row - self.check_data_integrity( - ('col1 INT',), - generator) - - def test_DECIMAL(self): - # DECIMAL - def generator(row,col): - from decimal import Decimal - return Decimal("%d.%02d" % (row, col)) - self.check_data_integrity( - ('col1 DECIMAL(5,2)',), - generator) - - def test_DATE(self): - ticks = time() - def generator(row,col): - return self.db_module.DateFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 DATE',), - generator) - - def test_TIME(self): - ticks = time() - def generator(row,col): - return self.db_module.TimeFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 TIME',), - generator) - - def test_DATETIME(self): - ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 DATETIME',), - generator) - - def test_TIMESTAMP(self): - ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313) - self.check_data_integrity( - ('col1 TIMESTAMP',), - generator) - - def test_fractional_TIMESTAMP(self): - ticks = time() - def generator(row,col): - return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313+row*0.7*col/3.0) - self.check_data_integrity( - ('col1 TIMESTAMP',), - generator) - - def test_LONG(self): - def generator(row,col): - if col == 0: - return row - else: - return self.BLOBUText # 'BLOB Text ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 LONG'), - generator) - - def test_TEXT(self): - def generator(row,col): - return self.BLOBUText # 'BLOB Text ' * 1024 - self.check_data_integrity( - ('col2 TEXT',), - generator) - - def test_LONG_BYTE(self): - def generator(row,col): - if col == 0: - return row - else: - return self.BLOBBinary # 'BLOB\000Binary ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 LONG BYTE'), - generator) - - def test_BLOB(self): - def generator(row,col): - if col == 0: - return row - else: - return self.BLOBBinary # 'BLOB\000Binary ' * 1024 - self.check_data_integrity( - ('col1 INT','col2 BLOB'), - generator) - +#!/usr/bin/env python -O +""" Script to test database capabilities and the DB-API interface + for functionality and memory leaks. + + Adapted from a script by M-A Lemburg. + +""" +from time import time +import unittest +from configdb import connection_factory + + +class DatabaseTest(unittest.TestCase): + db_module = None + connect_args = () + connect_kwargs = dict() + create_table_extra = "" + rows = 10 + debug = False + + def setUp(self): + db = connection_factory(**self.connect_kwargs) + self.connection = db + self.cursor = db.cursor() + self.BLOBUText = "".join([chr(i) for i in range(16384)]) + self.BLOBBinary = self.db_module.Binary( + ("".join([chr(i) for i in range(256)] * 16)).encode("latin1") + ) + + leak_test = True + + def tearDown(self): + if self.leak_test: + import gc + + del self.cursor + orphans = gc.collect() + self.assertFalse( + orphans, "%d orphaned objects found after deleting cursor" % orphans + ) + + del self.connection + orphans = gc.collect() + self.assertFalse( + orphans, "%d orphaned objects found after deleting connection" % orphans + ) + + def table_exists(self, name): + try: + self.cursor.execute("select * from %s where 1=0" % name) + except Exception: + return False + else: + return True + + def quote_identifier(self, ident): + return '"%s"' % ident + + def new_table_name(self): + i = id(self.cursor) + while True: + name = self.quote_identifier("tb%08x" % i) + if not self.table_exists(name): + return name + i = i + 1 + + def create_table(self, columndefs): + """Create a table using a list of column definitions given in + columndefs. + + generator must be a function taking arguments (row_number, + col_number) returning a suitable data object for insertion + into the table. + + """ + self.table = self.new_table_name() + self.cursor.execute( + "CREATE TABLE %s (%s) %s" + % (self.table, ",\n".join(columndefs), self.create_table_extra) + ) + + def check_data_integrity(self, columndefs, generator): + # insert + self.create_table(columndefs) + insert_statement = "INSERT INTO {} VALUES ({})".format( + self.table, + ",".join(["%s"] * len(columndefs)), + ) + data = [ + [generator(i, j) for j in range(len(columndefs))] for i in range(self.rows) + ] + self.cursor.executemany(insert_statement, data) + self.connection.commit() + # verify + self.cursor.execute("select * from %s" % self.table) + res = self.cursor.fetchall() + self.assertEqual(len(res), self.rows) + try: + for i in range(self.rows): + for j in range(len(columndefs)): + self.assertEqual(res[i][j], generator(i, j)) + finally: + if not self.debug: + self.cursor.execute("drop table %s" % (self.table)) + + def test_transactions(self): + columndefs = ("col1 INT", "col2 VARCHAR(255)") + + def generator(row, col): + if col == 0: + return row + else: + return ("%i" % (row % 10)) * 255 + + self.create_table(columndefs) + insert_statement = "INSERT INTO {} VALUES ({})".format( + self.table, + ",".join(["%s"] * len(columndefs)), + ) + data = [ + [generator(i, j) for j in range(len(columndefs))] for i in range(self.rows) + ] + self.cursor.executemany(insert_statement, data) + # verify + self.connection.commit() + self.cursor.execute("select * from %s" % self.table) + res = self.cursor.fetchall() + self.assertEqual(len(res), self.rows) + for i in range(self.rows): + for j in range(len(columndefs)): + 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(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(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)) + + def test_truncation(self): + columndefs = ("col1 INT", "col2 VARCHAR(255)") + + def generator(row, col): + if col == 0: + return row + else: + return ("%i" % (row % 10)) * ((255 - self.rows // 2) + row) + + self.create_table(columndefs) + insert_statement = "INSERT INTO {} VALUES ({})".format( + self.table, + ",".join(["%s"] * len(columndefs)), + ) + + try: + self.cursor.execute(insert_statement, (0, "0" * 256)) + except self.connection.DataError: + pass + else: + self.fail( + "Over-long column did not generate warnings/exception with single insert" # noqa: E501 + ) + + self.connection.rollback() + + try: + for i in range(self.rows): + data = [] + for j in range(len(columndefs)): + data.append(generator(i, j)) + self.cursor.execute(insert_statement, tuple(data)) + except self.connection.DataError: + pass + else: + self.fail( + "Over-long columns did not generate warnings/exception with execute()" # noqa: E501 + ) + + self.connection.rollback() + + try: + data = [ + [generator(i, j) for j in range(len(columndefs))] + for i in range(self.rows) + ] + self.cursor.executemany(insert_statement, data) + except self.connection.DataError: + pass + else: + self.fail( + "Over-long columns did not generate warnings/exception with executemany()" # noqa: E501 + ) + + self.connection.rollback() + self.cursor.execute("drop table %s" % (self.table)) + + def test_CHAR(self): + # Character data + def generator(row, col): + return ("%i" % ((row + col) % 10)) * 255 + + self.check_data_integrity(("col1 char(255)", "col2 char(255)"), generator) + + def test_INT(self): + # Number data + def generator(row, col): + return row * row + + self.check_data_integrity(("col1 INT",), generator) + + def test_DECIMAL(self): + # DECIMAL + from decimal import Decimal + + def generator(row, col): + return Decimal("%d.%02d" % (row, col)) + + self.check_data_integrity(("col1 DECIMAL(5,2)",), generator) + + val = Decimal("1.11111111111111119E-7") + self.cursor.execute("SELECT %s", (val,)) + result = self.cursor.fetchone()[0] + self.assertEqual(result, val) + self.assertIsInstance(result, Decimal) + + self.cursor.execute("SELECT %s + %s", (Decimal("0.1"), Decimal("0.2"))) + result = self.cursor.fetchone()[0] + self.assertEqual(result, Decimal("0.3")) + self.assertIsInstance(result, Decimal) + + def test_DATE(self): + ticks = time() + + def generator(row, col): + return self.db_module.DateFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 DATE",), generator) + + def test_TIME(self): + ticks = time() + + def generator(row, col): + return self.db_module.TimeFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 TIME",), generator) + + def test_DATETIME(self): + ticks = time() + + def generator(row, col): + return self.db_module.TimestampFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 DATETIME",), generator) + + def test_TIMESTAMP(self): + ticks = time() + + def generator(row, col): + return self.db_module.TimestampFromTicks(ticks + row * 86400 - col * 1313) + + self.check_data_integrity(("col1 TIMESTAMP",), generator) + + def test_fractional_TIMESTAMP(self): + ticks = time() + + def generator(row, col): + return self.db_module.TimestampFromTicks( + ticks + row * 86400 - col * 1313 + row * 0.7 * col / 3.0 + ) + + self.check_data_integrity(("col1 TIMESTAMP",), generator) + + def test_LONG(self): + def generator(row, col): + if col == 0: + return row + else: + return self.BLOBUText # 'BLOB Text ' * 1024 + + self.check_data_integrity(("col1 INT", "col2 LONG"), generator) + + def test_TEXT(self): + def generator(row, col): + return self.BLOBUText # 'BLOB Text ' * 1024 + + self.check_data_integrity(("col2 TEXT",), generator) + + def test_LONG_BYTE(self): + def generator(row, col): + if col == 0: + return row + else: + return self.BLOBBinary # 'BLOB\000Binary ' * 1024 + + self.check_data_integrity(("col1 INT", "col2 LONG BYTE"), generator) + + def test_BLOB(self): + def generator(row, col): + if col == 0: + return row + else: + return self.BLOBBinary # 'BLOB\000Binary ' * 1024 + + self.check_data_integrity(("col1 INT", "col2 BLOB"), generator) + + def test_DOUBLE(self): + for val in (18014398509481982.0, 0.1): + self.cursor.execute("SELECT %s", (val,)) + result = self.cursor.fetchone()[0] + self.assertEqual(result, val) + self.assertIsInstance(result, float) diff --git a/tests/configdb.py b/tests/configdb.py index 307cc3f4..c2949039 100644 --- a/tests/configdb.py +++ b/tests/configdb.py @@ -3,11 +3,11 @@ from os import environ, path tests_path = path.dirname(__file__) -conf_file = environ.get('TESTDB', 'default.cnf') +conf_file = environ.get("TESTDB", "default.cnf") conf_path = path.join(tests_path, conf_file) connect_kwargs = dict( - read_default_file = conf_path, - read_default_group = "MySQLdb-tests", + read_default_file=conf_path, + read_default_group="MySQLdb-tests", ) @@ -19,6 +19,7 @@ def connection_kwargs(kwargs): def connection_factory(**kwargs): import MySQLdb + db_kwargs = connection_kwargs(kwargs) db = MySQLdb.connect(**db_kwargs) return db diff --git a/tests/dbapi20.py b/tests/dbapi20.py index e1326d1b..be0f6292 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -''' Python DB API 2.0 driver compliance unit test suite. - +""" Python DB API 2.0 driver compliance unit test suite. + This software is Public Domain and may be used without restrictions. "Now we have booze and barflies entering the discussion, plus rumours of @@ -9,11 +9,11 @@ this is turning out to be a thoroughly unwholesome unit test." -- Ian Bicking -''' +""" -__rcs_id__ = '$Id$' -__version__ = '$Revision$'[11:-2] -__author__ = 'Stuart Bishop ' +__rcs_id__ = "$Id$" +__version__ = "$Revision$"[11:-2] +__author__ = "Stuart Bishop " import unittest import time @@ -52,11 +52,11 @@ # - Now a subclass of TestCase, to avoid requiring the driver stub # to use multiple inheritance # - Reversed the polarity of buggy test in test_description -# - Test exception heirarchy correctly +# - Test exception hierarchy correctly # - self.populate is now self._populate(), so if a driver stub -# overrides self.ddl1 this change propogates +# overrides self.ddl1 this change propagates # - VARCHAR columns now have a width, which will hopefully make the -# DDL even more portible (this will be reversed if it causes more problems) +# 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 @@ -64,69 +64,70 @@ # - Fix bugs in test_setoutputsize_basic and test_setinputsizes # + 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' # method is to be found driver = None - connect_args = () # List of arguments to pass to connect - connect_kw_args = {} # Keyword arguments for connect - table_prefix = 'dbapi20test_' # If you need to specify a prefix for tables + connect_args = () # List of arguments to pass to connect + connect_kw_args = {} # Keyword arguments for connect + table_prefix = "dbapi20test_" # If you need to specify a prefix for tables + + ddl1 = "create table %sbooze (name varchar(20))" % table_prefix + ddl2 = "create table %sbarflys (name varchar(20))" % table_prefix + xddl1 = "drop table %sbooze" % table_prefix + xddl2 = "drop table %sbarflys" % table_prefix - ddl1 = 'create table %sbooze (name varchar(20))' % table_prefix - ddl2 = 'create table %sbarflys (name varchar(20))' % table_prefix - xddl1 = 'drop table %sbooze' % table_prefix - xddl2 = 'drop table %sbarflys' % table_prefix + lowerfunc = "lower" # Name of stored procedure to convert string->lowercase - lowerfunc = 'lower' # Name of stored procedure to convert string->lowercase - # Some drivers may need to override these helpers, for example adding # a 'commit' after the execute. - def executeDDL1(self,cursor): + def executeDDL1(self, cursor): cursor.execute(self.ddl1) - def executeDDL2(self,cursor): + def executeDDL2(self, cursor): cursor.execute(self.ddl2) def setUp(self): - ''' self.drivers should override this method to perform required setup - 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: cur = con.cursor() - for ddl in (self.xddl1,self.xddl2): - try: + for ddl in (self.xddl1, self.xddl2): + try: cur.execute(ddl) con.commit() - except self.driver.Error: + except self.driver.Error: # Assume table didn't exist. Other tests will check if # execute is busted. pass @@ -135,9 +136,7 @@ def tearDown(self): def _connect(self): try: - return self.driver.connect( - *self.connect_args,**self.connect_kw_args - ) + return self.driver.connect(*self.connect_args, **self.connect_kw_args) except AttributeError: self.fail("No connect method found in self.driver module") @@ -150,7 +149,7 @@ def test_apilevel(self): # Must exist apilevel = self.driver.apilevel # Must equal 2.0 - self.assertEqual(apilevel,'2.0') + self.assertEqual(apilevel, "2.0") except AttributeError: self.fail("Driver doesn't define apilevel") @@ -159,7 +158,7 @@ def test_threadsafety(self): # Must exist threadsafety = self.driver.threadsafety # Must be a valid value - self.assertTrue(threadsafety in (0,1,2,3)) + self.assertTrue(threadsafety in (0, 1, 2, 3)) except AttributeError: self.fail("Driver doesn't define threadsafety") @@ -168,38 +167,24 @@ def test_paramstyle(self): # Must exist paramstyle = self.driver.paramstyle # Must be a valid value - self.assertTrue(paramstyle in ( - 'qmark','numeric','named','format','pyformat' - )) + self.assertTrue( + paramstyle in ("qmark", "numeric", "named", "format", "pyformat") + ) except AttributeError: self.fail("Driver doesn't define paramstyle") def test_Exceptions(self): # Make sure required exceptions exist, and are in the - # defined heirarchy. - self.assertTrue(issubclass(self.driver.Warning,Exception)) - self.assertTrue(issubclass(self.driver.Error,Exception)) - self.assertTrue( - issubclass(self.driver.InterfaceError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.DatabaseError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.OperationalError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.IntegrityError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.InternalError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.ProgrammingError,self.driver.Error) - ) - self.assertTrue( - issubclass(self.driver.NotSupportedError,self.driver.Error) - ) + # defined hierarchy. + self.assertTrue(issubclass(self.driver.Warning, Exception)) + self.assertTrue(issubclass(self.driver.Error, Exception)) + self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.DatabaseError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.OperationalError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.IntegrityError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.InternalError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.ProgrammingError, self.driver.Error)) + self.assertTrue(issubclass(self.driver.NotSupportedError, self.driver.Error)) def test_ExceptionsAsConnectionAttributes(self): # OPTIONAL EXTENSION @@ -220,7 +205,6 @@ def test_ExceptionsAsConnectionAttributes(self): self.assertTrue(con.ProgrammingError is drv.ProgrammingError) self.assertTrue(con.NotSupportedError is drv.NotSupportedError) - def test_commit(self): con = self._connect() try: @@ -233,16 +217,16 @@ def test_rollback(self): con = self._connect() # If rollback is defined, it should either work or throw # the documented exception - if hasattr(con,'rollback'): + if hasattr(con, "rollback"): try: con.rollback() except self.driver.NotSupportedError: pass - + def test_cursor(self): con = self._connect() try: - cur = con.cursor() + _ = con.cursor() finally: con.close() @@ -254,14 +238,14 @@ def test_cursor_isolation(self): cur1 = con.cursor() cur2 = con.cursor() self.executeDDL1(cur1) - cur1.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) + cur1.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) cur2.execute("select name from %sbooze" % self.table_prefix) booze = cur2.fetchall() - self.assertEqual(len(booze),1) - self.assertEqual(len(booze[0]),1) - self.assertEqual(booze[0][0],'Victoria Bitter') + self.assertEqual(len(booze), 1) + self.assertEqual(len(booze[0]), 1) + self.assertEqual(booze[0][0], "Victoria Bitter") finally: con.close() @@ -270,31 +254,41 @@ def test_description(self): try: cur = con.cursor() self.executeDDL1(cur) - self.assertEqual(cur.description,None, - 'cursor.description should be none after executing a ' - 'statement that can return no rows (such as DDL)' - ) - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(len(cur.description),1, - 'cursor.description describes too many columns' - ) - self.assertEqual(len(cur.description[0]),7, - 'cursor.description[x] tuples must have 7 elements' - ) - self.assertEqual(cur.description[0][0].lower(),'name', - 'cursor.description[x][0] must return column name' - ) - self.assertEqual(cur.description[0][1],self.driver.STRING, - 'cursor.description[x][1] must return column type. Got %r' - % cur.description[0][1] - ) + self.assertEqual( + cur.description, + None, + "cursor.description should be none after executing a " + "statement that can return no rows (such as DDL)", + ) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertEqual( + len(cur.description), 1, "cursor.description describes too many columns" + ) + self.assertEqual( + len(cur.description[0]), + 7, + "cursor.description[x] tuples must have 7 elements", + ) + self.assertEqual( + cur.description[0][0].lower(), + "name", + "cursor.description[x][0] must return column name", + ) + self.assertEqual( + cur.description[0][1], + self.driver.STRING, + "cursor.description[x][1] must return column type. Got %r" + % cur.description[0][1], + ) # Make sure self.description gets reset self.executeDDL2(cur) - self.assertEqual(cur.description,None, - 'cursor.description not being set to None when executing ' - 'no-result statements (eg. DDL)' - ) + self.assertEqual( + cur.description, + None, + "cursor.description not being set to None when executing " + "no-result statements (eg. DDL)", + ) finally: con.close() @@ -303,47 +297,49 @@ def test_rowcount(self): try: cur = con.cursor() self.executeDDL1(cur) - self.assertEqual(cur.rowcount,-1, - 'cursor.rowcount should be -1 after executing no-result ' - 'statements' - ) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number or rows inserted, or ' - 'set to -1 after executing an insert statement' - ) + self.assertEqual( + cur.rowcount, + -1, + "cursor.rowcount should be -1 after executing no-result " "statements", + ) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + self.assertTrue( + cur.rowcount in (-1, 1), + "cursor.rowcount should == number or rows inserted, or " + "set to -1 after executing an insert statement", + ) cur.execute("select name from %sbooze" % self.table_prefix) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number of rows returned, or ' - 'set to -1 after executing a select statement' - ) + self.assertTrue( + cur.rowcount in (-1, 1), + "cursor.rowcount should == number of rows returned, or " + "set to -1 after executing a select statement", + ) self.executeDDL2(cur) - self.assertEqual(cur.rowcount,-1, - 'cursor.rowcount not being reset to -1 after executing ' - 'no-result statements' - ) + self.assertEqual( + cur.rowcount, + -1, + "cursor.rowcount not being reset to -1 after executing " + "no-result statements", + ) finally: con.close() - lower_func = 'lower' + lower_func = "lower" + def test_callproc(self): con = self._connect() try: cur = con.cursor() - if self.lower_func and hasattr(cur,'callproc'): - r = cur.callproc(self.lower_func,('FOO',)) - self.assertEqual(len(r),1) - self.assertEqual(r[0],'FOO') + if self.lower_func and hasattr(cur, "callproc"): + r = cur.callproc(self.lower_func, ("FOO",)) + self.assertEqual(len(r), 1) + self.assertEqual(r[0], "FOO") r = cur.fetchall() - self.assertEqual(len(r),1,'callproc produced no result set') - self.assertEqual(len(r[0]),1, - 'callproc produced invalid result set' - ) - self.assertEqual(r[0][0],'foo', - 'callproc produced invalid results' - ) + self.assertEqual(len(r), 1, "callproc produced no result set") + self.assertEqual(len(r[0]), 1, "callproc produced invalid result set") + self.assertEqual(r[0][0], "foo", "callproc produced invalid results") finally: con.close() @@ -356,14 +352,14 @@ def test_close(self): # cursor.execute should raise an Error if called after connection # closed - self.assertRaises(self.driver.Error,self.executeDDL1,cur) + self.assertRaises(self.driver.Error, self.executeDDL1, cur) # connection.commit should raise an Error if called after connection' # closed.' - self.assertRaises(self.driver.Error,con.commit) + self.assertRaises(self.driver.Error, con.commit) # connection.close should raise an Error if called more than once - self.assertRaises(self.driver.Error,con.close) + self.assertRaises(self.driver.Error, con.close) def test_execute(self): con = self._connect() @@ -373,105 +369,99 @@ def test_execute(self): finally: con.close() - def _paraminsert(self,cur): + def _paraminsert(self, cur): self.executeDDL1(cur) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) - self.assertTrue(cur.rowcount in (-1,1)) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + self.assertTrue(cur.rowcount in (-1, 1)) - if self.driver.paramstyle == 'qmark': + if self.driver.paramstyle == "qmark": cur.execute( - 'insert into %sbooze values (?)' % self.table_prefix, - ("Cooper's",) - ) - elif self.driver.paramstyle == 'numeric': + "insert into %sbooze values (?)" % self.table_prefix, ("Cooper's",) + ) + elif self.driver.paramstyle == "numeric": cur.execute( - 'insert into %sbooze values (:1)' % self.table_prefix, - ("Cooper's",) - ) - elif self.driver.paramstyle == 'named': + "insert into %sbooze values (:1)" % self.table_prefix, ("Cooper's",) + ) + elif self.driver.paramstyle == "named": cur.execute( - 'insert into %sbooze values (:beer)' % self.table_prefix, - {'beer':"Cooper's"} - ) - elif self.driver.paramstyle == 'format': + "insert into %sbooze values (:beer)" % self.table_prefix, + {"beer": "Cooper's"}, + ) + elif self.driver.paramstyle == "format": cur.execute( - 'insert into %sbooze values (%%s)' % self.table_prefix, - ("Cooper's",) - ) - elif self.driver.paramstyle == 'pyformat': + "insert into %sbooze values (%%s)" % self.table_prefix, ("Cooper's",) + ) + elif self.driver.paramstyle == "pyformat": cur.execute( - 'insert into %sbooze values (%%(beer)s)' % self.table_prefix, - {'beer':"Cooper's"} - ) + "insert into %sbooze values (%%(beer)s)" % self.table_prefix, + {"beer": "Cooper's"}, + ) else: - self.fail('Invalid paramstyle') - self.assertTrue(cur.rowcount in (-1,1)) + self.fail("Invalid paramstyle") + self.assertTrue(cur.rowcount in (-1, 1)) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) res = cur.fetchall() - self.assertEqual(len(res),2,'cursor.fetchall returned too few rows') - beers = [res[0][0],res[1][0]] + self.assertEqual(len(res), 2, "cursor.fetchall returned too few rows") + beers = [res[0][0], res[1][0]] beers.sort() - self.assertEqual(beers[0],"Cooper's", - 'cursor.fetchall retrieved incorrect data, or data inserted ' - 'incorrectly' - ) - self.assertEqual(beers[1],"Victoria Bitter", - 'cursor.fetchall retrieved incorrect data, or data inserted ' - 'incorrectly' - ) + self.assertEqual( + beers[0], + "Cooper's", + "cursor.fetchall retrieved incorrect data, or data inserted " "incorrectly", + ) + self.assertEqual( + beers[1], + "Victoria Bitter", + "cursor.fetchall retrieved incorrect data, or data inserted " "incorrectly", + ) def test_executemany(self): con = self._connect() try: cur = con.cursor() self.executeDDL1(cur) - largs = [ ("Cooper's",) , ("Boag's",) ] - margs = [ {'beer': "Cooper's"}, {'beer': "Boag's"} ] - if self.driver.paramstyle == 'qmark': + largs = [("Cooper's",), ("Boag's",)] + margs = [{"beer": "Cooper's"}, {"beer": "Boag's"}] + if self.driver.paramstyle == "qmark": cur.executemany( - 'insert into %sbooze values (?)' % self.table_prefix, - largs - ) - elif self.driver.paramstyle == 'numeric': + "insert into %sbooze values (?)" % self.table_prefix, largs + ) + elif self.driver.paramstyle == "numeric": cur.executemany( - 'insert into %sbooze values (:1)' % self.table_prefix, - largs - ) - elif self.driver.paramstyle == 'named': + "insert into %sbooze values (:1)" % self.table_prefix, largs + ) + elif self.driver.paramstyle == "named": cur.executemany( - 'insert into %sbooze values (:beer)' % self.table_prefix, - margs - ) - elif self.driver.paramstyle == 'format': + "insert into %sbooze values (:beer)" % self.table_prefix, margs + ) + elif self.driver.paramstyle == "format": cur.executemany( - 'insert into %sbooze values (%%s)' % self.table_prefix, - largs - ) - elif self.driver.paramstyle == 'pyformat': + "insert into %sbooze values (%%s)" % self.table_prefix, largs + ) + elif self.driver.paramstyle == "pyformat": cur.executemany( - 'insert into %sbooze values (%%(beer)s)' % ( - self.table_prefix - ), - margs - ) - else: - self.fail('Unknown paramstyle') - self.assertTrue(cur.rowcount in (-1,2), - 'insert using cursor.executemany set cursor.rowcount to ' - 'incorrect value %r' % cur.rowcount + "insert into %sbooze values (%%(beer)s)" % (self.table_prefix), + margs, ) - cur.execute('select name from %sbooze' % self.table_prefix) + else: + self.fail("Unknown paramstyle") + self.assertTrue( + cur.rowcount in (-1, 2), + "insert using cursor.executemany set cursor.rowcount to " + "incorrect value %r" % cur.rowcount, + ) + cur.execute("select name from %sbooze" % self.table_prefix) res = cur.fetchall() - self.assertEqual(len(res),2, - 'cursor.fetchall retrieved incorrect number of rows' - ) - beers = [res[0][0],res[1][0]] + self.assertEqual( + len(res), 2, "cursor.fetchall retrieved incorrect number of rows" + ) + beers = [res[0][0], res[1][0]] beers.sort() - self.assertEqual(beers[0],"Boag's",'incorrect data retrieved') - self.assertEqual(beers[1],"Cooper's",'incorrect data retrieved') + self.assertEqual(beers[0], "Boag's", "incorrect data retrieved") + self.assertEqual(beers[1], "Cooper's", "incorrect data retrieved") finally: con.close() @@ -482,59 +472,61 @@ def test_fetchone(self): # cursor.fetchone should raise an Error if called before # executing a select-type query - self.assertRaises(self.driver.Error,cur.fetchone) + self.assertRaises(self.driver.Error, cur.fetchone) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows self.executeDDL1(cur) - self.assertRaises(self.driver.Error,cur.fetchone) + self.assertRaises(self.driver.Error, cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if a query retrieves ' - 'no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertEqual( + cur.fetchone(), + None, + "cursor.fetchone should return None if a query retrieves " "no rows", + ) + self.assertTrue(cur.rowcount in (-1, 0)) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) - self.assertRaises(self.driver.Error,cur.fetchone) + # executing a query that cannot return rows + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + self.assertRaises(self.driver.Error, cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchone() - self.assertEqual(len(r),1, - 'cursor.fetchone should have retrieved a single row' - ) - self.assertEqual(r[0],'Victoria Bitter', - 'cursor.fetchone retrieved incorrect data' - ) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if no more rows available' - ) - self.assertTrue(cur.rowcount in (-1,1)) + self.assertEqual( + len(r), 1, "cursor.fetchone should have retrieved a single row" + ) + self.assertEqual( + r[0], "Victoria Bitter", "cursor.fetchone retrieved incorrect data" + ) + self.assertEqual( + cur.fetchone(), + None, + "cursor.fetchone should return None if no more rows available", + ) + self.assertTrue(cur.rowcount in (-1, 1)) finally: con.close() samples = [ - 'Carlton Cold', - 'Carlton Draft', - 'Mountain Goat', - 'Redback', - 'Victoria Bitter', - 'XXXX' - ] + "Carlton Cold", + "Carlton Draft", + "Mountain Goat", + "Redback", + "Victoria Bitter", + "XXXX", + ] def _populate(self): - ''' Return a list of sql commands to setup the DB for the fetch - tests. - ''' + """Return a list of sql commands to setup the DB for the fetch + tests. + """ populate = [ - "insert into %sbooze values ('%s')" % (self.table_prefix,s) - for s in self.samples - ] + f"insert into {self.table_prefix}booze values ('{s}')" for s in self.samples + ] return populate def test_fetchmany(self): @@ -543,78 +535,88 @@ def test_fetchmany(self): cur = con.cursor() # cursor.fetchmany should raise an Error if called without - #issuing a query - self.assertRaises(self.driver.Error,cur.fetchmany,4) + # issuing a query + self.assertRaises(self.driver.Error, cur.fetchmany, 4) self.executeDDL1(cur) for sql in self._populate(): cur.execute(sql) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchmany() - self.assertEqual(len(r),1, - 'cursor.fetchmany retrieved incorrect number of rows, ' - 'default of arraysize is one.' - ) - cur.arraysize=10 - r = cur.fetchmany(3) # Should get 3 rows - self.assertEqual(len(r),3, - 'cursor.fetchmany retrieved incorrect number of rows' - ) - r = cur.fetchmany(4) # Should get 2 more - self.assertEqual(len(r),2, - 'cursor.fetchmany retrieved incorrect number of rows' - ) - r = cur.fetchmany(4) # Should be an empty sequence - self.assertEqual(len(r),0, - 'cursor.fetchmany should return an empty sequence after ' - 'results are exhausted' + self.assertEqual( + len(r), + 1, + "cursor.fetchmany retrieved incorrect number of rows, " + "default of arraysize is one.", + ) + cur.arraysize = 10 + r = cur.fetchmany(3) # Should get 3 rows + self.assertEqual( + len(r), 3, "cursor.fetchmany retrieved incorrect number of rows" + ) + r = cur.fetchmany(4) # Should get 2 more + self.assertEqual( + len(r), 2, "cursor.fetchmany retrieved incorrect number of rows" + ) + r = cur.fetchmany(4) # Should be an empty sequence + self.assertEqual( + len(r), + 0, + "cursor.fetchmany should return an empty sequence after " + "results are exhausted", ) - self.assertTrue(cur.rowcount in (-1,6)) + self.assertTrue(cur.rowcount in (-1, 6)) # Same as above, using cursor.arraysize - cur.arraysize=4 - cur.execute('select name from %sbooze' % self.table_prefix) - r = cur.fetchmany() # Should get 4 rows - self.assertEqual(len(r),4, - 'cursor.arraysize not being honoured by fetchmany' - ) - r = cur.fetchmany() # Should get 2 more - self.assertEqual(len(r),2) - r = cur.fetchmany() # Should be an empty sequence - self.assertEqual(len(r),0) - self.assertTrue(cur.rowcount in (-1,6)) - - cur.arraysize=6 - cur.execute('select name from %sbooze' % self.table_prefix) - rows = cur.fetchmany() # Should get all rows - self.assertTrue(cur.rowcount in (-1,6)) - self.assertEqual(len(rows),6) - self.assertEqual(len(rows),6) + cur.arraysize = 4 + cur.execute("select name from %sbooze" % self.table_prefix) + r = cur.fetchmany() # Should get 4 rows + self.assertEqual( + len(r), 4, "cursor.arraysize not being honoured by fetchmany" + ) + r = cur.fetchmany() # Should get 2 more + self.assertEqual(len(r), 2) + r = cur.fetchmany() # Should be an empty sequence + self.assertEqual(len(r), 0) + self.assertTrue(cur.rowcount in (-1, 6)) + + cur.arraysize = 6 + cur.execute("select name from %sbooze" % self.table_prefix) + rows = cur.fetchmany() # Should get all rows + self.assertTrue(cur.rowcount in (-1, 6)) + self.assertEqual(len(rows), 6) + self.assertEqual(len(rows), 6) rows = [r[0] for r in rows] rows.sort() - + # Make sure we get the right data back out - for i in range(0,6): - self.assertEqual(rows[i],self.samples[i], - 'incorrect data retrieved by cursor.fetchmany' - ) - - rows = cur.fetchmany() # Should return an empty list - self.assertEqual(len(rows),0, - 'cursor.fetchmany should return an empty sequence if ' - 'called after the whole result set has been fetched' + for i in range(0, 6): + self.assertEqual( + rows[i], + self.samples[i], + "incorrect data retrieved by cursor.fetchmany", ) - self.assertTrue(cur.rowcount in (-1,6)) + + rows = cur.fetchmany() # Should return an empty list + self.assertEqual( + len(rows), + 0, + "cursor.fetchmany should return an empty sequence if " + "called after the whole result set has been fetched", + ) + self.assertTrue(cur.rowcount in (-1, 6)) self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) - r = cur.fetchmany() # Should get empty sequence - self.assertEqual(len(r),0, - 'cursor.fetchmany should return an empty sequence if ' - 'query retrieved no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) + cur.execute("select name from %sbarflys" % self.table_prefix) + r = cur.fetchmany() # Should get empty sequence + self.assertEqual( + len(r), + 0, + "cursor.fetchmany should return an empty sequence if " + "query retrieved no rows", + ) + self.assertTrue(cur.rowcount in (-1, 0)) finally: con.close() @@ -633,41 +635,46 @@ def test_fetchall(self): cur.execute(sql) # cursor.fetchall should raise an Error if called - # after executing a a statement that cannot return rows - self.assertRaises(self.driver.Error,cur.fetchall) + # after executing a statement that cannot return rows + self.assertRaises(self.driver.Error, cur.fetchall) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,len(self.samples))) - self.assertEqual(len(rows),len(self.samples), - 'cursor.fetchall did not retrieve all rows' - ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) + self.assertEqual( + len(rows), + len(self.samples), + "cursor.fetchall did not retrieve all rows", + ) rows = [r[0] for r in rows] rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'cursor.fetchall retrieved incorrect rows' + for i in range(0, len(self.samples)): + self.assertEqual( + rows[i], self.samples[i], "cursor.fetchall retrieved incorrect rows" ) rows = cur.fetchall() self.assertEqual( - len(rows),0, - 'cursor.fetchall should return an empty list if called ' - 'after the whole result set has been fetched' - ) - self.assertTrue(cur.rowcount in (-1,len(self.samples))) + len(rows), + 0, + "cursor.fetchall should return an empty list if called " + "after the whole result set has been fetched", + ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) + cur.execute("select name from %sbarflys" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,0)) - self.assertEqual(len(rows),0, - 'cursor.fetchall should return an empty list if ' - 'a select query returns no rows' - ) - + self.assertTrue(cur.rowcount in (-1, 0)) + self.assertEqual( + len(rows), + 0, + "cursor.fetchall should return an empty list if " + "a select query returns no rows", + ) + finally: con.close() - + def test_mixedfetch(self): con = self._connect() try: @@ -676,91 +683,91 @@ def test_mixedfetch(self): for sql in self._populate(): cur.execute(sql) - cur.execute('select name from %sbooze' % self.table_prefix) - rows1 = cur.fetchone() + cur.execute("select name from %sbooze" % self.table_prefix) + rows1 = cur.fetchone() rows23 = cur.fetchmany(2) - rows4 = cur.fetchone() + rows4 = cur.fetchone() rows56 = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,6)) - self.assertEqual(len(rows23),2, - 'fetchmany returned incorrect number of rows' - ) - self.assertEqual(len(rows56),2, - 'fetchall returned incorrect number of rows' - ) + self.assertTrue(cur.rowcount in (-1, 6)) + self.assertEqual( + len(rows23), 2, "fetchmany returned incorrect number of rows" + ) + self.assertEqual( + len(rows56), 2, "fetchall returned incorrect number of rows" + ) rows = [rows1[0]] - rows.extend([rows23[0][0],rows23[1][0]]) + rows.extend([rows23[0][0], rows23[1][0]]) rows.append(rows4[0]) - rows.extend([rows56[0][0],rows56[1][0]]) + rows.extend([rows56[0][0], rows56[1][0]]) rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'incorrect data retrieved or inserted' - ) + for i in range(0, len(self.samples)): + self.assertEqual( + rows[i], self.samples[i], "incorrect data retrieved or inserted" + ) finally: con.close() - def help_nextset_setUp(self,cur): - ''' Should create a procedure called deleteme - that returns two result sets, first the - number of rows in booze then "name from booze" - ''' - raise NotImplementedError('Helper not implemented') - #sql=""" + def help_nextset_setUp(self, cur): + """Should create a procedure called deleteme + that returns two result sets, first the + number of rows in booze then "name from booze" + """ + raise NotImplementedError("Helper not implemented") + # sql=""" # create procedure deleteme as # begin # select count(*) from booze # select name from booze # end - #""" - #cur.execute(sql) + # """ + # cur.execute(sql) - def help_nextset_tearDown(self,cur): - 'If cleaning up is needed after nextSetTest' - raise NotImplementedError('Helper not implemented') - #cur.execute("drop procedure deleteme") + def help_nextset_tearDown(self, cur): + "If cleaning up is needed after nextSetTest" + raise NotImplementedError("Helper not implemented") + # cur.execute("drop procedure deleteme") def test_nextset(self): con = self._connect() try: cur = con.cursor() - if not hasattr(cur,'nextset'): + if not hasattr(cur, "nextset"): return try: self.executeDDL1(cur) - sql=self._populate() + sql = self._populate() for sql in self._populate(): cur.execute(sql) self.help_nextset_setUp(cur) - cur.callproc('deleteme') - numberofrows=cur.fetchone() - assert numberofrows[0]== len(self.samples) + cur.callproc("deleteme") + numberofrows = cur.fetchone() + assert numberofrows[0] == len(self.samples) assert cur.nextset() - names=cur.fetchall() + names = cur.fetchall() assert len(names) == len(self.samples) - s=cur.nextset() - assert s == None,'No more return sets, should return None' + s = cur.nextset() + assert s is None, "No more return sets, should return None" finally: self.help_nextset_tearDown(cur) finally: con.close() - def test_nextset(self): - raise NotImplementedError('Drivers need to override this test') + def test_nextset(self): # noqa: F811 + raise NotImplementedError("Drivers need to override this test") def test_arraysize(self): # Not much here - rest of the tests for this are in test_fetchmany con = self._connect() try: cur = con.cursor() - self.assertTrue(hasattr(cur,'arraysize'), - 'cursor.arraysize must be defined' - ) + self.assertTrue( + hasattr(cur, "arraysize"), "cursor.arraysize must be defined" + ) finally: con.close() @@ -768,8 +775,8 @@ def test_setinputsizes(self): con = self._connect() try: cur = con.cursor() - cur.setinputsizes( (25,) ) - self._paraminsert(cur) # Make sure cursor still works + cur.setinputsizes((25,)) + self._paraminsert(cur) # Make sure cursor still works finally: con.close() @@ -779,75 +786,74 @@ def test_setoutputsize_basic(self): try: cur = con.cursor() cur.setoutputsize(1000) - cur.setoutputsize(2000,0) - self._paraminsert(cur) # Make sure the cursor still works + cur.setoutputsize(2000, 0) + self._paraminsert(cur) # Make sure the cursor still works finally: con.close() def test_setoutputsize(self): - # Real test for setoutputsize is driver dependant - raise NotImplementedError('Driver need to override this test') + # Real test for setoutputsize is driver dependent + raise NotImplementedError("Driver need to override this test") def test_None(self): con = self._connect() try: cur = con.cursor() self.executeDDL1(cur) - cur.execute('insert into %sbooze values (NULL)' % self.table_prefix) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("insert into %sbooze values (NULL)" % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchall() - self.assertEqual(len(r),1) - self.assertEqual(len(r[0]),1) - self.assertEqual(r[0][0],None,'NULL value not returned as None') + self.assertEqual(len(r), 1) + self.assertEqual(len(r[0]), 1) + self.assertEqual(r[0][0], None, "NULL value not returned as None") finally: con.close() def test_Date(self): - d1 = self.driver.Date(2002,12,25) - d2 = self.driver.DateFromTicks(time.mktime((2002,12,25,0,0,0,0,0,0))) + d1 = self.driver.Date(2002, 12, 25) # noqa F841 + d2 = self.driver.DateFromTicks( # noqa F841 + time.mktime((2002, 12, 25, 0, 0, 0, 0, 0, 0)) + ) # Can we assume this? API doesn't specify, but it seems implied # self.assertEqual(str(d1),str(d2)) def test_Time(self): - t1 = self.driver.Time(13,45,30) - t2 = self.driver.TimeFromTicks(time.mktime((2001,1,1,13,45,30,0,0,0))) + t1 = self.driver.Time(13, 45, 30) # noqa F841 + t2 = self.driver.TimeFromTicks( # noqa F841 + time.mktime((2001, 1, 1, 13, 45, 30, 0, 0, 0)) + ) # Can we assume this? API doesn't specify, but it seems implied # self.assertEqual(str(t1),str(t2)) def test_Timestamp(self): - t1 = self.driver.Timestamp(2002,12,25,13,45,30) - t2 = self.driver.TimestampFromTicks( - time.mktime((2002,12,25,13,45,30,0,0,0)) - ) + t1 = self.driver.Timestamp(2002, 12, 25, 13, 45, 30) # noqa F841 + t2 = self.driver.TimestampFromTicks( # noqa F841 + time.mktime((2002, 12, 25, 13, 45, 30, 0, 0, 0)) + ) # Can we assume this? API doesn't specify, but it seems implied # self.assertEqual(str(t1),str(t2)) def test_Binary(self): - b = self.driver.Binary(b'Something') - b = self.driver.Binary(b'') + b = self.driver.Binary(b"Something") + b = self.driver.Binary(b"") # noqa F841 def test_STRING(self): - self.assertTrue(hasattr(self.driver,'STRING'), - 'module.STRING must be defined' - ) + self.assertTrue(hasattr(self.driver, "STRING"), "module.STRING must be defined") def test_BINARY(self): - self.assertTrue(hasattr(self.driver,'BINARY'), - 'module.BINARY must be defined.' - ) + self.assertTrue( + hasattr(self.driver, "BINARY"), "module.BINARY must be defined." + ) def test_NUMBER(self): - self.assertTrue(hasattr(self.driver,'NUMBER'), - 'module.NUMBER must be defined.' - ) + self.assertTrue( + hasattr(self.driver, "NUMBER"), "module.NUMBER must be defined." + ) def test_DATETIME(self): - self.assertTrue(hasattr(self.driver,'DATETIME'), - 'module.DATETIME must be defined.' - ) + self.assertTrue( + hasattr(self.driver, "DATETIME"), "module.DATETIME must be defined." + ) def test_ROWID(self): - self.assertTrue(hasattr(self.driver,'ROWID'), - 'module.ROWID must be defined.' - ) - + self.assertTrue(hasattr(self.driver, "ROWID"), "module.ROWID must be defined.") diff --git a/tests/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_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 1ec32f71..dbff27c2 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -1,164 +1,205 @@ -#!/usr/bin/env python -import capabilities -from datetime import timedelta -import unittest -import MySQLdb -import warnings - - -warnings.filterwarnings('ignore') - - -class test_MySQLdb(capabilities.DatabaseTest): - - db_module = MySQLdb - connect_args = () - connect_kwargs = dict(use_unicode=True, sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL") - create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8" - leak_test = False - - def quote_identifier(self, ident): - return "`%s`" % ident - - def test_TIME(self): - def generator(row,col): - return timedelta(0, row*8000) - self.check_data_integrity( - ('col1 TIME',), - generator) - - def test_TINYINT(self): - # Number data - def generator(row, col): - v = (row*row) % 256 - if v > 127: - v = v-256 - return v - self.check_data_integrity( - ('col1 TINYINT',), - generator) - - def test_stored_procedures(self): - db = self.connection - c = self.cursor - self.create_table(('pos INT', 'tree CHAR(20)')) - c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, - list(enumerate('ash birch cedar larch pine'.split()))) - db.commit() - - c.execute(""" - CREATE PROCEDURE test_sp(IN t VARCHAR(255)) - BEGIN - SELECT pos FROM %s WHERE tree = t; - END - """ % self.table) - db.commit() - - c.callproc('test_sp', ('larch',)) - rows = c.fetchall() - self.assertEqual(len(rows), 1) - self.assertEqual(rows[0][0], 3) - c.nextset() - - c.execute("DROP PROCEDURE test_sp") - c.execute('drop table %s' % (self.table)) - - def test_small_CHAR(self): - # Character data - def generator(row,col): - i = (row*col+62)%256 - if i == 62: return '' - if i == 63: return None - return chr(i) - self.check_data_integrity( - ('col1 char(1)','col2 char(1)'), - generator) - - def test_BIT(self): - c = self.cursor - try: - c.execute("""create table test_BIT ( - b3 BIT(3), - b7 BIT(10), - b64 BIT(64))""") - - one64 = '1'*64 - c.execute( - "insert into test_BIT (b3, b7, b64)" - " VALUES (b'011', b'1111111111', b'%s')" - % one64) - - c.execute("SELECT b3, b7, b64 FROM test_BIT") - row = c.fetchone() - self.assertEqual(row[0], b'\x03') - self.assertEqual(row[1], b'\x03\xff') - self.assertEqual(row[2], b'\xff'*8) - finally: - c.execute("drop table if exists test_BIT") - - def test_MULTIPOLYGON(self): - c = self.cursor - try: - c.execute("""create table test_MULTIPOLYGON ( - id INTEGER PRIMARY KEY, - border MULTIPOLYGON)""") - - c.execute( - "insert into test_MULTIPOLYGON (id, border)" - " VALUES (1, GeomFromText('MULTIPOLYGON(((1 1, 1 -1, -1 -1, -1 1, 1 1)),((1 1, 3 1, 3 3, 1 3, 1 1)))'))" - ) - - c.execute("SELECT id, AsText(border) FROM test_MULTIPOLYGON") - row = c.fetchone() - self.assertEqual(row[0], 1) - self.assertEqual(row[1], b'MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))') - - c.execute("SELECT id, AsWKB(border) FROM test_MULTIPOLYGON") - row = c.fetchone() - self.assertEqual(row[0], 1) - self.assertNotEqual(len(row[1]), 0) - - c.execute("SELECT id, border FROM test_MULTIPOLYGON") - row = c.fetchone() - self.assertEqual(row[0], 1) - self.assertNotEqual(len(row[1]), 0) - finally: - c.execute("drop table if exists test_MULTIPOLYGON") - - def test_bug_2671682(self): - from MySQLdb.constants import ER - try: - self.cursor.execute("describe some_non_existent_table"); - except self.connection.ProgrammingError as msg: - self.assertTrue(str(ER.NO_SUCH_TABLE) in str(msg)) - - def test_bug_3514287(self): - c = self.cursor - try: - c.execute("""create table bug_3541287 ( - c1 CHAR(10), - t1 TIMESTAMP)""") - c.execute("insert into bug_3541287 (c1,t1) values (%s, NOW())", - ("blah",)) - finally: - c.execute("drop table if exists bug_3541287") - - def test_ping(self): - self.connection.ping() - - def test_reraise_exception(self): - c = self.cursor - try: - c.execute("SELECT x FROM not_existing_table") - except MySQLdb.ProgrammingError as e: - self.assertEqual(e.args[0], 1146) - return - self.fail("Should raise ProgrammingError") - - -if __name__ == '__main__': - if test_MySQLdb.leak_test: - import gc - gc.enable() - gc.set_debug(gc.DEBUG_LEAK) - unittest.main() +#!/usr/bin/env python +import capabilities +from datetime import timedelta +from contextlib import closing +import unittest +import MySQLdb +from configdb import connection_factory +import warnings + + +warnings.filterwarnings("ignore") + + +class test_MySQLdb(capabilities.DatabaseTest): + db_module = MySQLdb + connect_args = () + connect_kwargs = dict( + use_unicode=True, sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL" + ) + create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8" + leak_test = False + + def quote_identifier(self, ident): + return "`%s`" % ident + + def test_TIME(self): + def generator(row, col): + return timedelta(0, row * 8000) + + self.check_data_integrity(("col1 TIME",), generator) + + def test_TINYINT(self): + # Number data + def generator(row, col): + v = (row * row) % 256 + if v > 127: + v = v - 256 + return v + + self.check_data_integrity(("col1 TINYINT",), generator) + + def test_stored_procedures(self): + db = self.connection + c = self.cursor + self.create_table(("pos INT", "tree CHAR(20)")) + c.executemany( + "INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table, + list(enumerate("ash birch cedar Lärche pine".split())), + ) + db.commit() + + c.execute( + """ + CREATE PROCEDURE test_sp(IN t VARCHAR(255)) + BEGIN + SELECT pos FROM %s WHERE tree = t; + END + """ + % self.table + ) + db.commit() + + c.callproc("test_sp", ("Lärche",)) + rows = c.fetchall() + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0][0], 3) + c.nextset() + + c.execute("DROP PROCEDURE test_sp") + c.execute("drop table %s" % (self.table)) + + def test_small_CHAR(self): + # Character data + def generator(row, col): + i = (row * col + 62) % 256 + if i == 62: + return "" + if i == 63: + return None + return chr(i) + + self.check_data_integrity(("col1 char(1)", "col2 char(1)"), generator) + + def test_BIT(self): + c = self.cursor + try: + c.execute( + """create table test_BIT ( + b3 BIT(3), + b7 BIT(10), + b64 BIT(64))""" + ) + + one64 = "1" * 64 + c.execute( + "insert into test_BIT (b3, b7, b64)" + " VALUES (b'011', b'1111111111', b'%s')" % one64 + ) + + c.execute("SELECT b3, b7, b64 FROM test_BIT") + row = c.fetchone() + self.assertEqual(row[0], b"\x03") + self.assertEqual(row[1], b"\x03\xff") + self.assertEqual(row[2], b"\xff" * 8) + finally: + c.execute("drop table if exists test_BIT") + + def test_MULTIPOLYGON(self): + c = self.cursor + try: + c.execute( + """create table test_MULTIPOLYGON ( + id INTEGER PRIMARY KEY, + border MULTIPOLYGON)""" + ) + + c.execute( + """ +INSERT INTO test_MULTIPOLYGON + (id, border) +VALUES (1, + 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, ST_AsText(border) FROM test_MULTIPOLYGON") + row = c.fetchone() + self.assertEqual(row[0], 1) + self.assertEqual( + row[1], + "MULTIPOLYGON(((1 1,1 -1,-1 -1,-1 1,1 1)),((1 1,3 1,3 3,1 3,1 1)))", + ) + + c.execute("SELECT id, ST_AsWKB(border) FROM test_MULTIPOLYGON") + row = c.fetchone() + self.assertEqual(row[0], 1) + self.assertNotEqual(len(row[1]), 0) + + c.execute("SELECT id, border FROM test_MULTIPOLYGON") + row = c.fetchone() + self.assertEqual(row[0], 1) + self.assertNotEqual(len(row[1]), 0) + finally: + c.execute("drop table if exists test_MULTIPOLYGON") + + def test_bug_2671682(self): + from MySQLdb.constants import ER + + try: + self.cursor.execute("describe some_non_existent_table") + except self.connection.ProgrammingError as msg: + self.assertTrue(str(ER.NO_SUCH_TABLE) in str(msg)) + + def test_bug_3514287(self): + c = self.cursor + try: + c.execute( + """create table bug_3541287 ( + c1 CHAR(10), + t1 TIMESTAMP)""" + ) + c.execute("insert into bug_3541287 (c1,t1) values (%s, NOW())", ("blah",)) + finally: + c.execute("drop table if exists bug_3541287") + + def test_ping(self): + self.connection.ping() + + def test_reraise_exception(self): + c = self.cursor + try: + c.execute("SELECT x FROM not_existing_table") + except MySQLdb.ProgrammingError as e: + self.assertEqual(e.args[0], 1146) + return + self.fail("Should raise ProgrammingError") + + def test_binary_prefix(self): + # verify prefix behaviour when enabled, disabled and for default (disabled) + for binary_prefix in (True, False, None): + kwargs = self.connect_kwargs.copy() + # needs to be set to can guarantee CHARSET response for normal strings + kwargs["charset"] = "utf8mb4" + if binary_prefix is not None: + kwargs["binary_prefix"] = binary_prefix + + with closing(connection_factory(**kwargs)) as conn: + with closing(conn.cursor()) as c: + c.execute("SELECT CHARSET(%s)", (MySQLdb.Binary(b"raw bytes"),)) + self.assertEqual( + c.fetchall()[0][0], "binary" if binary_prefix else "utf8mb4" + ) + # normal strings should not get prefix + c.execute("SELECT CHARSET(%s)", ("str",)) + self.assertEqual(c.fetchall()[0][0], "utf8mb4") + + +if __name__ == "__main__": + if test_MySQLdb.leak_test: + import gc + + gc.enable() + gc.set_debug(gc.DEBUG_LEAK) + unittest.main() diff --git a/tests/test_MySQLdb_dbapi20.py b/tests/test_MySQLdb_dbapi20.py index 3fef3f5f..a0dd92a1 100644 --- a/tests/test_MySQLdb_dbapi20.py +++ b/tests/test_MySQLdb_dbapi20.py @@ -4,23 +4,28 @@ import MySQLdb from configdb import connection_kwargs import warnings + warnings.simplefilter("ignore") class test_MySQLdb(dbapi20.DatabaseAPI20Test): driver = MySQLdb connect_args = () - connect_kw_args = connection_kwargs(dict(sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")) + connect_kw_args = connection_kwargs( + dict(sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL") + ) + + def test_setoutputsize(self): + pass - def test_setoutputsize(self): pass - def test_setoutputsize_basic(self): pass - def test_nextset(self): pass + def test_setoutputsize_basic(self): + pass """The tests on fetchone and fetchall and rowcount bogusly test for an exception if the statement cannot return a result set. MySQL always returns a result set; it's just that some things return empty result sets.""" - + def test_fetchall(self): con = self._connect() try: @@ -35,41 +40,46 @@ def test_fetchall(self): cur.execute(sql) # cursor.fetchall should raise an Error if called - # after executing a a statement that cannot return rows - #self.assertRaises(self.driver.Error,cur.fetchall) + # after executing a statement that cannot return rows + # self.assertRaises(self.driver.Error,cur.fetchall) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,len(self.samples))) - self.assertEqual(len(rows),len(self.samples), - 'cursor.fetchall did not retrieve all rows' - ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) + self.assertEqual( + len(rows), + len(self.samples), + "cursor.fetchall did not retrieve all rows", + ) rows = [r[0] for r in rows] rows.sort() - for i in range(0,len(self.samples)): - self.assertEqual(rows[i],self.samples[i], - 'cursor.fetchall retrieved incorrect rows' + for i in range(0, len(self.samples)): + self.assertEqual( + rows[i], self.samples[i], "cursor.fetchall retrieved incorrect rows" ) rows = cur.fetchall() self.assertEqual( - len(rows),0, - 'cursor.fetchall should return an empty list if called ' - 'after the whole result set has been fetched' - ) - self.assertTrue(cur.rowcount in (-1,len(self.samples))) + len(rows), + 0, + "cursor.fetchall should return an empty list if called " + "after the whole result set has been fetched", + ) + self.assertTrue(cur.rowcount in (-1, len(self.samples))) self.executeDDL2(cur) - cur.execute('select name from %sbarflys' % self.table_prefix) + cur.execute("select name from %sbarflys" % self.table_prefix) rows = cur.fetchall() - self.assertTrue(cur.rowcount in (-1,0)) - self.assertEqual(len(rows),0, - 'cursor.fetchall should return an empty list if ' - 'a select query returns no rows' - ) - + self.assertTrue(cur.rowcount in (-1, 0)) + self.assertEqual( + len(rows), + 0, + "cursor.fetchall should return an empty list if " + "a select query returns no rows", + ) + finally: con.close() - + def test_fetchone(self): con = self._connect() try: @@ -77,39 +87,42 @@ def test_fetchone(self): # cursor.fetchone should raise an Error if called before # executing a select-type query - self.assertRaises(self.driver.Error,cur.fetchone) + self.assertRaises(self.driver.Error, cur.fetchone) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows self.executeDDL1(cur) -## self.assertRaises(self.driver.Error,cur.fetchone) + # self.assertRaises(self.driver.Error,cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) - self.assertEqual(cur.fetchone(),None, - 'cursor.fetchone should return None if a query retrieves ' - 'no rows' - ) - self.assertTrue(cur.rowcount in (-1,0)) + cur.execute("select name from %sbooze" % self.table_prefix) + self.assertEqual( + cur.fetchone(), + None, + "cursor.fetchone should return None if a query retrieves " "no rows", + ) + self.assertTrue(cur.rowcount in (-1, 0)) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) -## self.assertRaises(self.driver.Error,cur.fetchone) + # executing a query that cannot return rows + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + # self.assertRaises(self.driver.Error,cur.fetchone) - cur.execute('select name from %sbooze' % self.table_prefix) + cur.execute("select name from %sbooze" % self.table_prefix) r = cur.fetchone() - self.assertEqual(len(r),1, - 'cursor.fetchone should have retrieved a single row' - ) - self.assertEqual(r[0],'Victoria Bitter', - 'cursor.fetchone retrieved incorrect data' - ) -## self.assertEqual(cur.fetchone(),None, -## 'cursor.fetchone should return None if no more rows available' -## ) - self.assertTrue(cur.rowcount in (-1,1)) + self.assertEqual( + len(r), 1, "cursor.fetchone should have retrieved a single row" + ) + self.assertEqual( + r[0], "Victoria Bitter", "cursor.fetchone retrieved incorrect data" + ) + # self.assertEqual( + # cur.fetchone(), + # None, + # "cursor.fetchone should return None if no more rows available", + # ) + self.assertTrue(cur.rowcount in (-1, 1)) finally: con.close() @@ -119,87 +132,100 @@ def test_rowcount(self): try: cur = con.cursor() self.executeDDL1(cur) -## self.assertEqual(cur.rowcount,-1, -## 'cursor.rowcount should be -1 after executing no-result ' -## 'statements' -## ) - cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( - self.table_prefix - )) -## self.assertTrue(cur.rowcount in (-1,1), -## 'cursor.rowcount should == number or rows inserted, or ' -## 'set to -1 after executing an insert statement' -## ) + # self.assertEqual(cur.rowcount,-1, + # 'cursor.rowcount should be -1 after executing no-result ' + # 'statements' + # ) + cur.execute( + "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) + ) + # self.assertTrue(cur.rowcount in (-1,1), + # 'cursor.rowcount should == number or rows inserted, or ' + # 'set to -1 after executing an insert statement' + # ) cur.execute("select name from %sbooze" % self.table_prefix) - self.assertTrue(cur.rowcount in (-1,1), - 'cursor.rowcount should == number of rows returned, or ' - 'set to -1 after executing a select statement' - ) + self.assertTrue( + cur.rowcount in (-1, 1), + "cursor.rowcount should == number of rows returned, or " + "set to -1 after executing a select statement", + ) self.executeDDL2(cur) -## self.assertEqual(cur.rowcount,-1, -## 'cursor.rowcount not being reset to -1 after executing ' -## 'no-result statements' -## ) + # self.assertEqual(cur.rowcount,-1, + # 'cursor.rowcount not being reset to -1 after executing ' + # 'no-result statements' + # ) finally: con.close() def test_callproc(self): - pass # performed in test_MySQL_capabilities - - def help_nextset_setUp(self,cur): - ''' Should create a procedure called deleteme - that returns two result sets, first the - number of rows in booze then "name from booze" - ''' - sql=""" + pass # performed in test_MySQL_capabilities + + def help_nextset_setUp(self, cur): + """ + Should create a procedure called deleteme + that returns two result sets, first the + number of rows in booze then "name from booze" + """ + sql = """ create procedure deleteme() begin select count(*) from %(tp)sbooze; select name from %(tp)sbooze; end - """ % dict(tp=self.table_prefix) + """ % dict( + tp=self.table_prefix + ) cur.execute(sql) - def help_nextset_tearDown(self,cur): - 'If cleaning up is needed after nextSetTest' + def help_nextset_tearDown(self, cur): + "If cleaning up is needed after nextSetTest" cur.execute("drop procedure deleteme") def test_nextset(self): - #from warnings import warn + # from warnings import warn + con = self._connect() try: cur = con.cursor() - if not hasattr(cur, 'nextset'): + if not hasattr(cur, "nextset"): return try: self.executeDDL1(cur) - sql=self._populate() + sql = self._populate() for sql in self._populate(): cur.execute(sql) self.help_nextset_setUp(cur) - cur.callproc('deleteme') - numberofrows=cur.fetchone() - assert numberofrows[0]== len(self.samples) + cur.callproc("deleteme") + numberofrows = cur.fetchone() + assert numberofrows[0] == len(self.samples) assert cur.nextset() - names=cur.fetchall() + names = cur.fetchall() assert len(names) == len(self.samples) - s=cur.nextset() + s = cur.nextset() if s: empty = cur.fetchall() - self.assertEqual(len(empty), 0, - "non-empty result set after other result sets") - #warn("Incompatibility: MySQL returns an empty result set for the CALL itself", - # Warning) - #assert s == None,'No more return sets, should return None' + self.assertEqual( + len(empty), 0, "non-empty result set after other result sets" + ) + # warn( + # ": ".join( + # [ + # "Incompatibility", + # "MySQL returns an empty result set for the CALL itself" + # ] + # ), + # Warning, + # ) + # assert s == None, "No more return sets, should return None" finally: self.help_nextset_tearDown(cur) finally: con.close() - -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_MySQLdb_nonstandard.py b/tests/test_MySQLdb_nonstandard.py index 7efe265c..5e841791 100644 --- a/tests/test_MySQLdb_nonstandard.py +++ b/tests/test_MySQLdb_nonstandard.py @@ -1,10 +1,11 @@ import unittest -import _mysql +from MySQLdb import _mysql import MySQLdb from MySQLdb.constants import FIELD_TYPE from configdb import connection_factory import warnings + warnings.simplefilter("ignore") @@ -25,10 +26,6 @@ def test_set_inequality_membership(self): class TestCoreModule(unittest.TestCase): """Core _mysql module features.""" - def test_NULL(self): - """Should have a NULL constant.""" - self.assertEqual(_mysql.NULL, 'NULL') - def test_version(self): """Version information sanity.""" self.assertTrue(isinstance(_mysql.__version__, str)) @@ -39,14 +36,13 @@ def test_version(self): def test_client_info(self): self.assertTrue(isinstance(_mysql.get_client_info(), str)) - def test_thread_safe(self): - self.assertTrue(isinstance(_mysql.thread_safe(), int)) - def test_escape_string(self): - self.assertEqual(_mysql.escape_string(b'foo"bar'), - b'foo\\"bar', "escape byte string") - self.assertEqual(_mysql.escape_string(u'foo"bar'), - b'foo\\"bar', "escape unicode string") + self.assertEqual( + _mysql.escape_string(b'foo"bar'), b'foo\\"bar', "escape byte string" + ) + self.assertEqual( + _mysql.escape_string('foo"bar'), b'foo\\"bar', "escape unicode string" + ) class CoreAPI(unittest.TestCase): @@ -60,35 +56,91 @@ def tearDown(self): def test_thread_id(self): tid = self.conn.thread_id() - self.assertTrue(isinstance(tid, int), - "thread_id didn't return an int.") + self.assertTrue(isinstance(tid, int), "thread_id didn't return an int.") - self.assertRaises(TypeError, self.conn.thread_id, ('evil',), - "thread_id shouldn't accept arguments.") + self.assertRaises( + TypeError, + self.conn.thread_id, + ("evil",), + "thread_id shouldn't accept arguments.", + ) def test_affected_rows(self): - self.assertEqual(self.conn.affected_rows(), 0, - "Should return 0 before we do anything.") - + self.assertEqual( + self.conn.affected_rows(), 0, "Should return 0 before we do anything." + ) - #def test_debug(self): - ## FIXME Only actually tests if you lack SUPER - #self.assertRaises(MySQLdb.OperationalError, - #self.conn.dump_debug_info) + # def test_debug(self): + # (FIXME) Only actually tests if you lack SUPER + # self.assertRaises(MySQLdb.OperationalError, + # self.conn.dump_debug_info) def test_charset_name(self): - self.assertTrue(isinstance(self.conn.character_set_name(), str), - "Should return a string.") + self.assertTrue( + isinstance(self.conn.character_set_name(), str), "Should return a string." + ) def test_host_info(self): - self.assertTrue(isinstance(self.conn.get_host_info(), str), - "Should return a string.") + self.assertTrue( + isinstance(self.conn.get_host_info(), str), "Should return a string." + ) def test_proto_info(self): - self.assertTrue(isinstance(self.conn.get_proto_info(), int), - "Should return an int.") + self.assertTrue( + isinstance(self.conn.get_proto_info(), int), "Should return an int." + ) def test_server_info(self): - self.assertTrue(isinstance(self.conn.get_server_info(), str), - "Should return an str.") + self.assertTrue( + isinstance(self.conn.get_server_info(), str), "Should return a string." + ) + + def test_client_flag(self): + conn = connection_factory( + use_unicode=True, client_flag=MySQLdb.constants.CLIENT.FOUND_ROWS + ) + + self.assertIsInstance(conn.client_flag, int) + self.assertTrue(conn.client_flag & MySQLdb.constants.CLIENT.FOUND_ROWS) + with self.assertRaises(AttributeError): + conn.client_flag = 0 + + conn.close() + + def test_fileno(self): + self.assertGreaterEqual(self.conn.fileno(), 0) + + def test_context_manager(self): + with connection_factory() as conn: + self.assertFalse(conn.closed) + self.assertTrue(conn.closed) + + +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") diff --git a/tests/test_MySQLdb_times.py b/tests/test_MySQLdb_times.py index 8000f645..2081b1ac 100644 --- a/tests/test_MySQLdb_times.py +++ b/tests/test_MySQLdb_times.py @@ -1,121 +1,146 @@ -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") class TestX_or_None(unittest.TestCase): def test_date_or_none(self): - assert times.Date_or_None('1969-01-01') == date(1969, 1, 1) - assert times.Date_or_None('2015-01-01') == date(2015, 1, 1) - assert times.Date_or_None('2015-12-13') == date(2015, 12, 13) + assert times.Date_or_None("1969-01-01") == date(1969, 1, 1) + assert times.Date_or_None("2015-01-01") == date(2015, 1, 1) + assert times.Date_or_None("2015-12-13") == date(2015, 12, 13) - assert times.Date_or_None('') is None - assert times.Date_or_None('fail') is None - assert times.Date_or_None('2015-12') is None - assert times.Date_or_None('2015-12-40') is None - assert times.Date_or_None('0000-00-00') is None + assert times.Date_or_None("") is None + assert times.Date_or_None("fail") is None + assert times.Date_or_None("2015-12") is None + assert times.Date_or_None("2015-12-40") is None + assert times.Date_or_None("0000-00-00") is None def test_time_or_none(self): - assert times.Time_or_None('00:00:00') == time(0, 0) - assert times.Time_or_None('01:02:03') == time(1, 2, 3) - assert times.Time_or_None('01:02:03.123456') == time(1, 2, 3, 123456) + assert times.Time_or_None("00:00:00") == time(0, 0) + assert times.Time_or_None("01:02:03") == time(1, 2, 3) + assert times.Time_or_None("01:02:03.123456") == time(1, 2, 3, 123456) - assert times.Time_or_None('') is None - assert times.Time_or_None('fail') is None - assert times.Time_or_None('24:00:00') is None - assert times.Time_or_None('01:02:03.123456789') is None + assert times.Time_or_None("") is None + assert times.Time_or_None("fail") is None + assert times.Time_or_None("24:00:00") is None + assert times.Time_or_None("01:02:03.123456789") is None def test_datetime_or_none(self): - assert times.DateTime_or_None('1000-01-01') == date(1000, 1, 1) - assert times.DateTime_or_None('2015-12-13') == date(2015, 12, 13) - assert times.DateTime_or_None('2015-12-13 01:02') == datetime(2015, 12, 13, 1, 2) - assert times.DateTime_or_None('2015-12-13T01:02') == datetime(2015, 12, 13, 1, 2) - assert times.DateTime_or_None('2015-12-13 01:02:03') == datetime(2015, 12, 13, 1, 2, 3) - assert times.DateTime_or_None('2015-12-13T01:02:03') == datetime(2015, 12, 13, 1, 2, 3) - assert times.DateTime_or_None('2015-12-13 01:02:03.123') == datetime(2015, 12, 13, 1, 2, 3, 123000) - assert times.DateTime_or_None('2015-12-13 01:02:03.000123') == datetime(2015, 12, 13, 1, 2, 3, 123) - assert times.DateTime_or_None('2015-12-13 01:02:03.123456') == datetime(2015, 12, 13, 1, 2, 3, 123456) - assert times.DateTime_or_None('2015-12-13T01:02:03.123456') == datetime(2015, 12, 13, 1, 2, 3, 123456) - - assert times.DateTime_or_None('') is None - assert times.DateTime_or_None('fail') is None - assert times.DateTime_or_None('0000-00-00 00:00:00') is None - assert times.DateTime_or_None('0000-00-00 00:00:00.000000') is None - assert times.DateTime_or_None('2015-12-13T01:02:03.123456789') is None + assert times.DateTime_or_None("1000-01-01") == date(1000, 1, 1) + assert times.DateTime_or_None("2015-12-13") == date(2015, 12, 13) + assert times.DateTime_or_None("2015-12-13 01:02") == datetime( + 2015, 12, 13, 1, 2 + ) + assert times.DateTime_or_None("2015-12-13T01:02") == datetime( + 2015, 12, 13, 1, 2 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03") == datetime( + 2015, 12, 13, 1, 2, 3 + ) + assert times.DateTime_or_None("2015-12-13T01:02:03") == datetime( + 2015, 12, 13, 1, 2, 3 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03.123") == datetime( + 2015, 12, 13, 1, 2, 3, 123000 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03.000123") == datetime( + 2015, 12, 13, 1, 2, 3, 123 + ) + assert times.DateTime_or_None("2015-12-13 01:02:03.123456") == datetime( + 2015, 12, 13, 1, 2, 3, 123456 + ) + assert times.DateTime_or_None("2015-12-13T01:02:03.123456") == datetime( + 2015, 12, 13, 1, 2, 3, 123456 + ) + + assert times.DateTime_or_None("") is None + assert times.DateTime_or_None("fail") is None + assert times.DateTime_or_None("0000-00-00 00:00:00") is None + assert times.DateTime_or_None("0000-00-00 00:00:00.000000") is None + assert times.DateTime_or_None("2015-12-13T01:02:03.123456789") is None def test_timedelta_or_none(self): - assert times.TimeDelta_or_None('-1:0:0') == timedelta(0, -3600) - assert times.TimeDelta_or_None('1:0:0') == timedelta(0, 3600) - assert times.TimeDelta_or_None('12:55:30') == timedelta(0, 46530) - assert times.TimeDelta_or_None('12:55:30.123456') == timedelta(0, 46530, 123456) - assert times.TimeDelta_or_None('12:55:30.123456789') == timedelta(0, 46653, 456789) - assert times.TimeDelta_or_None('12:55:30.123456789123456') == timedelta(1429, 37719, 123456) - - assert times.TimeDelta_or_None('') is None - assert times.TimeDelta_or_None('0') is None - assert times.TimeDelta_or_None('fail') is None + assert times.TimeDelta_or_None("-1:0:0") == timedelta(0, -3600) + assert times.TimeDelta_or_None("1:0:0") == timedelta(0, 3600) + assert times.TimeDelta_or_None("12:55:30") == timedelta(0, 46530) + assert times.TimeDelta_or_None("12:55:30.123456") == timedelta(0, 46530, 123456) + assert times.TimeDelta_or_None("12:55:30.123456789") == timedelta( + 0, 46653, 456789 + ) + assert times.TimeDelta_or_None("12:55:30.123456789123456") == timedelta( + 1429, 37719, 123456 + ) + + assert times.TimeDelta_or_None("") is None + assert times.TimeDelta_or_None("0") is None + assert times.TimeDelta_or_None("fail") is None class TestTicks(unittest.TestCase): - @mock.patch('MySQLdb.times.localtime', side_effect=gmtime) + @mock.patch("MySQLdb.times.localtime", side_effect=gmtime) def test_date_from_ticks(self, mock): assert times.DateFromTicks(0) == date(1970, 1, 1) assert times.DateFromTicks(1430000000) == date(2015, 4, 25) - @mock.patch('MySQLdb.times.localtime', side_effect=gmtime) + @mock.patch("MySQLdb.times.localtime", side_effect=gmtime) def test_time_from_ticks(self, mock): assert times.TimeFromTicks(0) == time(0, 0, 0) assert times.TimeFromTicks(1431100000) == time(15, 46, 40) assert times.TimeFromTicks(1431100000.123) == time(15, 46, 40) - @mock.patch('MySQLdb.times.localtime', side_effect=gmtime) + @mock.patch("MySQLdb.times.localtime", side_effect=gmtime) def test_timestamp_from_ticks(self, mock): assert times.TimestampFromTicks(0) == datetime(1970, 1, 1, 0, 0, 0) assert times.TimestampFromTicks(1430000000) == datetime(2015, 4, 25, 22, 13, 20) - assert times.TimestampFromTicks(1430000000.123) == datetime(2015, 4, 25, 22, 13, 20) - - -class TestTimestampConverter(unittest.TestCase): - def test_mysql_timestamp_converter(self): - assert times.mysql_timestamp_converter('2015-12-13') == date(2015, 12, 13) - assert times.mysql_timestamp_converter('2038-01-19 03:14:07') == datetime(2038, 1, 19, 3, 14, 7) - - assert times.mysql_timestamp_converter('2015121310') == datetime(2015, 12, 13, 10, 0) - assert times.mysql_timestamp_converter('20151213101112') == datetime(2015, 12, 13, 10, 11, 12) - - assert times.mysql_timestamp_converter('20151313') is None - assert times.mysql_timestamp_converter('2015-13-13') is None + assert times.TimestampFromTicks(1430000000.123) == datetime( + 2015, 4, 25, 22, 13, 20 + ) class TestToLiteral(unittest.TestCase): def test_datetime_to_literal(self): - assert times.DateTime2literal(datetime(2015, 12, 13), '') == b"'2015-12-13 00:00:00'" - assert times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13), '') == b"'2015-12-13 11:12:13'" - assert times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13, 123456), '') == b"'2015-12-13 11:12:13.123456'" + self.assertEqual( + times.DateTime2literal(datetime(2015, 12, 13), ""), b"'2015-12-13 00:00:00'" + ) + self.assertEqual( + times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13), ""), + b"'2015-12-13 11:12:13'", + ) + self.assertEqual( + times.DateTime2literal(datetime(2015, 12, 13, 11, 12, 13, 123456), ""), + b"'2015-12-13 11:12:13.123456'", + ) def test_datetimedelta_to_literal(self): d = datetime(2015, 12, 13, 1, 2, 3) - datetime(2015, 12, 13, 1, 2, 2) - assert times.DateTimeDelta2literal(d, '') == b"'0 0:0:1'" + assert times.DateTimeDelta2literal(d, "") == b"'0 0:0:1'" class TestFormat(unittest.TestCase): def test_format_timedelta(self): d = datetime(2015, 1, 1) - datetime(2015, 1, 1) - assert times.format_TIMEDELTA(d) == '0 0:0:0' + assert times.format_TIMEDELTA(d) == "0 0:0:0" d = datetime(2015, 1, 1, 10, 11, 12) - datetime(2015, 1, 1, 8, 9, 10) - assert times.format_TIMEDELTA(d) == '0 2:2:2' + assert times.format_TIMEDELTA(d) == "0 2:2:2" d = datetime(2015, 1, 1, 10, 11, 12) - datetime(2015, 1, 1, 11, 12, 13) - assert times.format_TIMEDELTA(d) == '-1 22:58:59' + assert times.format_TIMEDELTA(d) == "-1 22:58:59" def test_format_timestamp(self): - assert times.format_TIMESTAMP(datetime(2015, 2, 3)) == '2015-02-03 00:00:00' - assert times.format_TIMESTAMP(datetime(2015, 2, 3, 17, 18, 19)) == '2015-02-03 17:18:19' - assert times.format_TIMESTAMP(datetime(15, 2, 3, 17, 18, 19)) == '0015-02-03 17:18:19' + assert times.format_TIMESTAMP(datetime(2015, 2, 3)) == "2015-02-03 00:00:00" + self.assertEqual( + times.format_TIMESTAMP(datetime(2015, 2, 3, 17, 18, 19)), + "2015-02-03 17:18:19", + ) + self.assertEqual( + times.format_TIMESTAMP(datetime(15, 2, 3, 17, 18, 19)), + "0015-02-03 17:18:19", + ) diff --git a/tests/test__mysql.py b/tests/test__mysql.py new file mode 100644 index 00000000..dd024b72 --- /dev/null +++ b/tests/test__mysql.py @@ -0,0 +1,7 @@ +import pytest +from MySQLdb import _mysql + + +def test_result_type(): + with pytest.raises(TypeError): + _mysql.result(b"xyz") 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,),) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index bfdcb33a..1d2c3655 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,4 +1,4 @@ -import py.test +import pytest import MySQLdb.cursors from configdb import connection_factory @@ -6,6 +6,7 @@ _conns = [] _tables = [] + def connect(**kwargs): conn = connection_factory(**kwargs) _conns.append(conn) @@ -17,7 +18,7 @@ def teardown_function(function): c = _conns[0] cur = c.cursor() for t in _tables: - cur.execute("DROP TABLE %s" % (t,)) + cur.execute(f"DROP TABLE {t}") cur.close() del _tables[:] @@ -33,42 +34,212 @@ def test_executemany(): cursor.execute("create table test (data varchar(10))") _tables.append("test") - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%s, %s)") - assert m is not None, 'error parse %s' - assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' - - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id)s, %(name)s)") - assert m is not None, 'error parse %(name)s' - assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' - - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s)") - assert m is not None, 'error parse %(id_name)s' - assert m.group(3) == '', 'group 3 not blank, bug in RE_INSERT_VALUES?' - - m = MySQLdb.cursors.RE_INSERT_VALUES.match("INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s) ON duplicate update") - assert m is not None, 'error parse %(id_name)s' - assert m.group(3) == ' ON duplicate update', 'group 3 not ON duplicate update, bug in RE_INSERT_VALUES?' - - # cursor._executed myst bee "insert into test (data) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)" + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%s, %s)" + ) + assert m is not None, "error parse %s" + assert m.group(3) == "", "group 3 not blank, bug in RE_INSERT_VALUES?" + + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%(id)s, %(name)s)" + ) + assert m is not None, "error parse %(name)s" + assert m.group(3) == "", "group 3 not blank, bug in RE_INSERT_VALUES?" + + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s)" + ) + assert m is not None, "error parse %(id_name)s" + assert m.group(3) == "", "group 3 not blank, bug in RE_INSERT_VALUES?" + + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO TEST (ID, NAME) VALUES (%(id_name)s, %(name)s) ON duplicate update" + ) + assert m is not None, "error parse %(id_name)s" + assert ( + m.group(3) == " ON duplicate update" + ), "group 3 not ON duplicate update, bug in RE_INSERT_VALUES?" + + # https://github.com/PyMySQL/mysqlclient-python/issues/178 + m = MySQLdb.cursors.RE_INSERT_VALUES.match( + "INSERT INTO bloup(foo, bar)VALUES(%s, %s)" + ) + assert m is not None + + # cursor._executed myst bee + # """ + # insert into test (data) + # values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9) + # """ # list args - data = range(10) + data = [(i,) for i in range(10)] cursor.executemany("insert into test (data) values (%s)", data) - assert cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %s not in one query' + assert cursor._executed.endswith( + b",(7),(8),(9)" + ), "execute many with %s not in one query" # dict args - data_dict = [{'data': i} for i in range(10)] + data_dict = [{"data": i} for i in range(10)] cursor.executemany("insert into test (data) values (%(data)s)", data_dict) - assert cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %(data)s not in one query' + assert cursor._executed.endswith( + b",(7),(8),(9)" + ), "execute many with %(data)s not in one query" # %% in column set - cursor.execute("""\ + cursor.execute( + """\ CREATE TABLE percent_test ( `A%` INTEGER, - `B%` INTEGER)""") + `B%` INTEGER)""" + ) try: q = "INSERT INTO percent_test (`A%%`, `B%%`) VALUES (%s, %s)" assert MySQLdb.cursors.RE_INSERT_VALUES.match(q) is not None cursor.executemany(q, [(3, 4), (5, 6)]) - assert cursor._executed.endswith(b"(3, 4),(5, 6)"), "executemany with %% not in one query" + assert cursor._executed.endswith( + b"(3, 4),(5, 6)" + ), "executemany with %% not in one query" finally: cursor.execute("DROP TABLE IF EXISTS percent_test") + + +def test_pyparam(): + conn = connect() + cursor = conn.cursor() + + cursor.execute("SELECT %(a)s, %(b)s", {"a": 1, "b": 2}) + assert cursor._executed == b"SELECT 1, 2" + cursor.execute(b"SELECT %(a)s, %(b)s", {b"a": 3, b"b": 4}) + 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 + + +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() + + +# 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") + + +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": "{}"}), + ) diff --git a/tests/test_errors.py b/tests/test_errors.py new file mode 100644 index 00000000..fae28e81 --- /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(f"DROP TABLE {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)") diff --git a/tests/travis.cnf b/tests/travis.cnf index 483b76f2..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 = default-character-set = utf8mb4 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 4c7fe5fd..00000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -[tox] -envlist = py26, py27, pypy, py33, py34, py35 - -[testenv] -passenv = TESTDB -commands = - py.test --cov {envsitepackagesdir}/MySQLdb -deps = - mock - coverage==3.7.1 - pytest - pytest-cov