├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── _config.yml ├── docs ├── examples │ └── basic01.py ├── requirements.txt └── source │ ├── api.rst │ ├── bugs.rst │ ├── conf.py │ ├── connection.rst │ ├── constants.rst │ ├── cursor.rst │ ├── faq.rst │ ├── index.rst │ ├── install.rst │ ├── license.rst │ ├── module.rst │ ├── pool.rst │ ├── pooling.rst │ ├── release.rst │ └── usage.rst ├── helper └── create_errconst.py ├── include ├── docs │ ├── common.h │ ├── connection.h │ ├── cursor.h │ ├── exception.h │ └── module.h └── mariadb_python.h ├── mariadb ├── __init__.py ├── connectionpool.py ├── connections.py ├── constants │ ├── CAPABILITY.py │ ├── CLIENT.py │ ├── CURSOR.py │ ├── ERR.py │ ├── EXT_FIELD_TYPE.py │ ├── FIELD_FLAG.py │ ├── FIELD_TYPE.py │ ├── INDICATOR.py │ ├── INFO.py │ ├── STATUS.py │ ├── TPC_STATE.py │ └── __init__.py ├── cursors.py ├── dbapi20.py ├── field.py ├── mariadb.c ├── mariadb_codecs.c ├── mariadb_connection.c ├── mariadb_cursor.c ├── mariadb_exception.c └── mariadb_parser.c ├── mariadb_posix.py ├── mariadb_windows.py ├── pyproject.toml ├── setup.py ├── site.cfg └── testing ├── bench.py ├── bench_init.py ├── benchmarks ├── README.md ├── benchmark │ ├── bulk.py │ ├── do_1.py │ ├── do_1000_param.py │ ├── select_1.py │ ├── select_1000_rows.py │ └── select_100_cols.py ├── internal_bench.py └── setup_db.py └── test ├── __init__.py ├── base_test.py ├── conf_test.py └── integration ├── __init__.py ├── test_connection.py ├── test_converter.py ├── test_cursor.py ├── test_cursor_mariadb.py ├── test_cursor_mysql.py ├── test_dbapi20.py ├── test_exception.py ├── test_module.py ├── test_nondbapi.py ├── test_pooling.py └── test_xa.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalise line endings: 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Run CI Tests 3 | 4 | on: 5 | push: 6 | branches: ['1.1', '1.0'] 7 | pull_request: 8 | workflow_dispatch: 9 | schedule: 10 | # Run weekly on Sundays at 2 AM UTC 11 | - cron: '0 2 * * 0' 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: false 16 | 17 | env: 18 | MYSQL_TEST_HOST: mariadb.example.com 19 | MYSQL_TEST_PORT: 3306 20 | MYSQL_TEST_USER: root 21 | MYSQL_TEST_PASSWD: "heyPassw-!*20oRd" 22 | MYSQL_TEST_DB: testp 23 | 24 | jobs: 25 | 26 | setup: 27 | runs-on: ubuntu-latest 28 | if: | 29 | github.event_name != 'schedule' || 30 | github.ref == 'refs/heads/3.4' || 31 | github.ref == 'refs/heads/3.1' || 32 | github.ref == 'refs/heads/3.2' 33 | outputs: 34 | matrix: ${{ steps.set-matrix.outputs.final-matrix }} 35 | steps: 36 | - uses: actions/checkout@v4 37 | - id: set-matrix 38 | name: build matrix 39 | uses: rusher/mariadb-test-build-matrix@main 40 | with: 41 | additional-matrix: '[{"name": "MariaDB 11.4", "os": "ubuntu-latest", "db-type": "community", "db-tag": "11.4", "python": "3.9"},{"name": "MariaDB 11.4", "os": "ubuntu-latest", "db-type": "community", "db-tag": "11.4", "python": "3.10"},{"name": "MariaDB 11.4", "os": "ubuntu-latest", "db-type": "community", "db-tag": "11.4", "python": "3.11"},{"name": "MariaDB 11.4", "os": "ubuntu-latest", "db-type": "community", "db-tag": "11.4", "python": "3.12"},{"name": "MariaDB 11.4", "os": "ubuntu-latest", "db-type": "community", "db-tag": "11.4", "python": "pypy3.11", "continue-on-error": true}]' 42 | 43 | ci: 44 | name: ${{ matrix.name }} ${{ matrix.python != '' && format(' - python {0}', matrix.python) || '' }} 45 | needs: setup 46 | timeout-minutes: 50 47 | strategy: 48 | matrix: ${{ fromJSON(needs.setup.outputs.matrix) }} 49 | 50 | runs-on: ${{ matrix.os }} 51 | continue-on-error: ${{ matrix.continue-on-error || false }} 52 | steps: 53 | - uses: actions/checkout@v4 54 | 55 | - name: Setup Test Environment 56 | id: setup-env 57 | uses: rusher/mariadb-test-setup@master 58 | with: 59 | node-version: ${{ matrix.node }} 60 | db-type: ${{ matrix.db-type }} 61 | db-tag: ${{ matrix.db-tag }} 62 | test-db-password: ${{ env.MYSQL_TEST_PASSWD }} 63 | test-db-database: ${{ env.MYSQL_TEST_DB }} 64 | test-db-port: ${{ env.MYSQL_TEST_PORT }} 65 | additional-conf: ${{ matrix.additional-conf || '' }} 66 | registry-user: ${{ matrix.db-type == 'enterprise' && secrets.ENTERPRISE_USER || (secrets.DOCKER_PWD != '' && 'mariadbtest' || '') }} 67 | registry-password: ${{ matrix.db-type == 'enterprise' && secrets.ENTERPRISE_TOKEN || secrets.DOCKER_PWD }} 68 | os: ${{ matrix.os }} 69 | 70 | - uses: actions/setup-python@v5 71 | id: setup-python 72 | with: 73 | python-version: ${{ matrix.python || '3.13' }} 74 | 75 | - name: Clone C/C 76 | uses: GuillaumeFalourd/clone-github-repo-action@v2.3 77 | with: 78 | branch: '3.4' 79 | owner: 'mariadb-corporation' 80 | repository: 'mariadb-connector-c' 81 | 82 | - name: c/c make ubuntu 83 | if: ${{ startsWith(matrix.os, 'ubuntu') }} 84 | run: | 85 | cd ${{ github.workspace }}/mariadb-connector-c 86 | cmake . -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB=On -DCMAKE_INSTALL_PREFIX=/usr 87 | make -j4 88 | sudo make install 89 | echo "MARIADB_PLUGIN_DIR=`mariadb_config --plugindir`" >> $GITHUB_ENV 90 | echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb" >> $GITHUB_ENV 91 | 92 | - name: c/c make macos 93 | if: ${{ startsWith(matrix.os, 'mac') }} 94 | run: | 95 | cd ${{ github.workspace }}/mariadb-connector-c 96 | cmake . -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB=On 97 | make -j4 98 | sudo make install 99 | ls -lrt /usr/local/lib/mariadb/plugin 100 | echo "MARIADB_PLUGIN_DIR=`mariadb_config --plugindir`" >> $GITHUB_ENV 101 | echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb" >> $GITHUB_ENV 102 | echo "DYLD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/mariadb:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV 103 | echo "DYLD_FALLBACK_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/mariadb" >> $GITHUB_ENV 104 | 105 | - name: c/c make windows 106 | if: ${{ startsWith(matrix.os, 'windows') }} 107 | shell: powershell 108 | run: | 109 | cd ${{ github.workspace }}/mariadb-connector-c 110 | $MARIADB_CC_INSTALL_DIR = "$env:USERPROFILE/conc" 111 | cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_CURL=ON -DCMAKE_INSTALL_PREFIX="$MARIADB_CC_INSTALL_DIR" 112 | cmake --build . --config RelWithDebInfo 113 | cmake --install . --config RelWithDebInfo 114 | echo "MARIADB_CC_INSTALL_DIR=$MARIADB_CC_INSTALL_DIR" >> $env:GITHUB_ENV 115 | echo "MARIADB_PLUGIN_DIR=$MARIADB_CC_INSTALL_DIR/lib/mariadb/plugin" >> $env:GITHUB_ENV 116 | 117 | - name: Run test suite 118 | shell: bash 119 | run: | 120 | python --version 121 | python -m pip install . 122 | cd testing 123 | python -m unittest discover -v 124 | env: 125 | TEST_DB_USER: ${{ env.MYSQL_TEST_USER }} 126 | TEST_DB_HOST: ${{ env.MYSQL_TEST_HOST }} 127 | TEST_DB_DATABASE: ${{ env.MYSQL_TEST_DB }} 128 | TEST_DB_PORT: ${{ env.MYSQL_TEST_PORT }} 129 | TEST_DB_PASSWORD: ${{ env.MYSQL_TEST_PASSWD }} 130 | LOCAL_DB: ${{ steps.setup-env.outputs.database-type }} 131 | PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Generate and Update API Docs 2 | 3 | on: 4 | workflow_dispatch: # Allow manual triggering 5 | release: 6 | types: [published] # Runs when release is published 7 | 8 | jobs: 9 | update-docs: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Python project 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: '3.12' 22 | 23 | 24 | - name: Clone C/C 25 | uses: GuillaumeFalourd/clone-github-repo-action@v2.3 26 | with: 27 | branch: '3.4' 28 | owner: 'mariadb-corporation' 29 | repository: 'mariadb-connector-c' 30 | 31 | - name: c/c make ubuntu 32 | run: | 33 | cd ${{ github.workspace }}/mariadb-connector-c 34 | cmake . -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB=On -DCMAKE_INSTALL_PREFIX=/usr 35 | make -j4 36 | sudo make install 37 | echo "MARIADB_PLUGIN_DIR=`mariadb_config --plugindir`" >> $GITHUB_ENV 38 | echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb" >> $GITHUB_ENV 39 | 40 | 41 | - name: Install dependencies 42 | run: | 43 | # Install your project dependencies 44 | pip install -r docs/requirements.txt 45 | pip install -e . 46 | 47 | - name: Generate Sphinx documentation 48 | run: | 49 | sphinx-build -b markdown docs/source docs/_build/markdown 50 | 51 | - name: Ensure fork exists and is up to date 52 | uses: actions/github-script@v7 53 | with: 54 | github-token: ${{ secrets.SPHINX_TOKEN }} 55 | script: | 56 | const owner = 'rusher'; 57 | const repo = 'mariadb-docs'; 58 | const upstream = 'mariadb-corporation'; 59 | const upstreamRepo = 'mariadb-docs'; 60 | // Check if fork exists 61 | let forkExists = false; 62 | try { 63 | await github.rest.repos.get({ owner, repo }); 64 | forkExists = true; 65 | } catch (e) { 66 | forkExists = false; 67 | } 68 | // Create fork if not exists 69 | if (!forkExists) { 70 | await github.rest.repos.createFork({ owner: upstream, repo: upstreamRepo }); 71 | // Wait for fork to be ready 72 | let ready = false; 73 | for (let i = 0; i < 10; i++) { 74 | try { 75 | await github.rest.repos.get({ owner, repo }); 76 | ready = true; 77 | break; 78 | } catch (e) { 79 | await new Promise(res => setTimeout(res, 5000)); 80 | } 81 | } 82 | if (!ready) throw new Error('Fork not ready after waiting.'); 83 | } 84 | 85 | - name: Checkout documentation repository (fork) 86 | uses: actions/checkout@v4 87 | with: 88 | repository: rusher/mariadb-docs 89 | token: ${{ secrets.SPHINX_TOKEN }} 90 | path: mariadb-docs 91 | ref: main 92 | 93 | - name: Add upstream and fetch latest main 94 | run: | 95 | cd mariadb-docs 96 | git remote add upstream https://github.com/mariadb-corporation/mariadb-docs.git || true 97 | git fetch upstream 98 | git checkout main 99 | git pull upstream main 100 | 101 | - name: Update documentation subdirectory 102 | run: | 103 | # Remove existing documentation in target subdirectory 104 | 105 | mkdir -p mariadb-docs/connectors/mariadb-connector-python/api/ 106 | rm -rf mariadb-docs/connectors/mariadb-connector-python/api/* 107 | # Copy new documentation 108 | cp -r docs/_build/markdown/* mariadb-docs/connectors/mariadb-connector-python/api/ 109 | 110 | # Optional: Add any additional processing here 111 | # e.g., update index files, fix relative links, etc. 112 | 113 | - name: Commit and push changes to fork 114 | run: | 115 | cd mariadb-docs 116 | git config user.name "github-actions[bot]" 117 | git config user.email "github-actions[bot]@users.noreply.github.com" 118 | git checkout -b auto-docs-update-${{ github.run_number }} 119 | git add connectors/mariadb-connector-python/api/ 120 | git commit -m "Update API documentation from ${{ github.repository }}" 121 | git push https://x-access-token:${{ secrets.SPHINX_TOKEN }}@github.com/rusher/mariadb-docs.git auto-docs-update-${{ github.run_number }} 122 | 123 | - name: Create Pull Request to Upstream 124 | uses: actions/github-script@v7 125 | with: 126 | github-token: ${{ secrets.SPHINX_TOKEN }} 127 | script: | 128 | const branch = `auto-docs-update-${{ github.run_number }}`; 129 | const prTitle = "Auto-update: API Documentation from ${{ github.repository }}"; 130 | const prBody = `This PR automatically updates the documentation subdirectory with the latest Sphinx-generated markdown from [${{ github.repository }}](${{ github.server_url }}/${{ github.repository }}).\n**Changes:**\n- Updated documentation generated from commit ${{ github.sha }}\n- Generated on: ${{ github.run_id }}`; 131 | try { 132 | // Check if a PR already exists for this branch 133 | const { data: pulls } = await github.rest.pulls.list({ 134 | owner: 'mariadb-corporation', 135 | repo: 'mariadb-docs', 136 | head: `rusher:${branch}`, 137 | base: 'main', 138 | state: 'open', 139 | }); 140 | if (pulls.length === 0) { 141 | const pr = await github.rest.pulls.create({ 142 | owner: 'mariadb-corporation', 143 | repo: 'mariadb-docs', 144 | title: prTitle, 145 | head: `rusher:${branch}`, 146 | base: 'main', 147 | body: prBody, 148 | maintainer_can_modify: false 149 | }); 150 | console.log('PR created: ', pr.data.html_url); 151 | } else { 152 | console.log('PR already exists: ', pulls[0].html_url); 153 | } 154 | } catch (error) { 155 | core.setFailed(`Failed to create PR: ${error.message}`); 156 | } 157 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Keep empty directories: 2 | # Keep empty directories: >> .gitignore/.git* 3 | 4 | # Compiled Static libraries 5 | *.lib 6 | *.a 7 | *.la 8 | *.lai 9 | *.lo 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.so.* 14 | *.dylib 15 | *.dll 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | *.dgcov 26 | .*.swp 27 | .gdb_history 28 | 29 | # Build/Dist 30 | build/ 31 | dist/ 32 | 33 | # Cache 34 | __pycache__ 35 | 36 | #VS files/directories 37 | *.vcxproj 38 | *.filters 39 | *.user 40 | ipch 41 | *.sln 42 | *.suo 43 | *.sdf 44 | Win32 45 | x64 46 | *.dir 47 | Debug 48 | Release 49 | RelWithDebInfo 50 | 51 | #vim backups 52 | *.*~ 53 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mariadb-connector-c"] 2 | path = mariadb-connector-c 3 | url = https://github.com/MariaDB/mariadb-connector-c.git 4 | branch = 3.0 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pycqa/flake8 3 | rev: '5.0.4' # pick a git hash / tag to point to 4 | hooks: 5 | - id: flake8 6 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: all 18 | 19 | # Ensure the necessary Python dependencies are installed 20 | python: 21 | install: 22 | - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: c 3 | 4 | before_install: 5 | - export MAIN_PATH=`pwd` 6 | # install pyenv to test multiple python version 7 | - |- 8 | if [ "$TRAVIS_OS_NAME" == "windows" ] ; then 9 | choco install pyenv-win 10 | export PYENV_ROOT=$HOME/.pyenv 11 | export PATH=$PYENV_ROOT/pyenv-win/bin:$PYENV_ROOT/pyenv-win/shims:$PATH 12 | pyenv update 13 | else 14 | git clone https://github.com/pyenv/pyenv.git ~/.pyenv 15 | export PYENV_ROOT="$HOME/.pyenv" 16 | export PATH="$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH" 17 | eval "$(pyenv init -)" 18 | fi 19 | # install c dependency 20 | - |- 21 | if [ "$TRAVIS_OS_NAME" == "linux" ] ; then 22 | sudo apt-get install software-properties-common 23 | sudo apt-get install -f libssl-dev libssl1.1 24 | sudo apt-get install -f 25 | fi 26 | - git clone https://github.com/mariadb-corporation/mariadb-connector-c.git ~/.cc_3 27 | - cd ~/.cc_3 28 | - mkdir bld 29 | - cd bld 30 | - |- 31 | case $TRAVIS_OS_NAME in 32 | windows) 33 | export WIX="c:/Program Files (x86)/WiX Toolset v3.14" 34 | export MARIADB_CC_INSTALL_DIR=$HOME/conc 35 | cmake .. -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$MARIADB_CC_INSTALL_DIR 36 | cmake --build . --config RelWithDebInfo 37 | cmake --install . --config RelWithDebInfo 38 | ;; 39 | osx) 40 | cmake .. -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB:BOOL=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 41 | make -j4 42 | ;; 43 | linux) 44 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr 45 | make -j4 46 | ;; 47 | esac 48 | - |- 49 | if [ "$TRAVIS_OS_NAME" != "windows" ] ; then 50 | sudo make install 51 | export MARIADB_PLUGIN_DIR==`mariadb_config --plugindir` 52 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb 53 | # install "install-latest" to retrieve latest python version corresponding to major.minor version 54 | git clone https://github.com/momo-lab/pyenv-install-latest.git $PYENV_ROOT/plugins/pyenv-install-latest 55 | export REAL_PYTHON_VERSION=$(pyenv install-latest --print $PYTHON_VER) 56 | else 57 | export MARIADB_PLUGIN_DIR=$MARIADB_CC_INSTALL/lib/mariadb/plugin 58 | # On Windows we test against latest stable only 59 | export REAL_PYTHON_VERSION=$(pyenv install --list | grep --extended-regexp "^\s*[0-9][0-9.]*[0-9]\s*$" | tail -1 | tr -d ' ') 60 | fi 61 | 62 | - echo $REAL_PYTHON_VERSION 63 | - pyenv install $REAL_PYTHON_VERSION 64 | - export PYENV_VERSION=$REAL_PYTHON_VERSION 65 | - pyenv versions 66 | # install server 67 | - cd $MAIN_PATH 68 | 69 | env: 70 | global: PYTHON_VER="3.10" HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 DB=testp CLEAR_TEXT=0 71 | 72 | import: mariadb-corporation/connector-test-machine:common-build.yml@master 73 | 74 | jobs: 75 | include: 76 | - stage: Language 77 | env: srv=mariadb v=10.11 local=1 PYTHON_VER="3.9" 78 | name: "Python 3.9" 79 | - env: srv=mariadb v=10.11 local=1 PYTHON_VER="3.10" 80 | name: "Python 3.10" 81 | - env: srv=mariadb v=10.11 local=1 PYTHON_VER="3.11" 82 | name: "Python 3.11" 83 | - env: srv=mariadb v=10.11 local=1 PYTHON_VER="3.12" 84 | name: "Python 3.12" 85 | - env: srv=mariadb v=10.11 local=1 PYTHON_VER="3.13" 86 | name: "Python 3.13" 87 | 88 | script: 89 | - python --version 90 | - python -m pip install . 91 | # - python setup.py build 92 | # - python setup.py install 93 | - cd testing 94 | - |- 95 | if [ -z "$BENCH" ] ; then 96 | python -m unittest discover -v 97 | else 98 | pip install mysql-connector-python pymysql pyperf 99 | export TEST_MODULE=mariadb 100 | python bench_init.py --inherit-environ=TEST_MODULE,TEST_DB_USER,TEST_DB_HOST,TEST_DB_DATABASE,TEST_DB_PORT,TEST_REQUIRE_TLS,TEST_DB_PASSWORD --copy-env 101 | python bench.py -o mariadb_bench.json --inherit-environ=TEST_MODULE,TEST_DB_USER,TEST_DB_HOST,TEST_DB_DATABASE,TEST_DB_PORT,TEST_REQUIRE_TLS,TEST_DB_PASSWORD --copy-env 102 | export TEST_MODULE=mysql.connector 103 | python bench.py -o mysql-connector-python_bench.json --inherit-environ=TEST_MODULE,TEST_DB_USER,TEST_DB_HOST,TEST_DB_DATABASE,TEST_DB_PORT,TEST_REQUIRE_TLS,TEST_DB_PASSWORD --copy-env 104 | export TEST_MODULE=pymysql 105 | python bench.py -o pymysql_bench.json --inherit-environ=TEST_MODULE,TEST_DB_USER,TEST_DB_HOST,TEST_DB_DATABASE,TEST_DB_PORT,TEST_REQUIRE_TLS,TEST_DB_PASSWORD --copy-env 106 | python -m pyperf compare_to pymysql_bench.json mysql-connector-python_bench.json mariadb_bench.json --table 107 | fi 108 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include testing/test/ *.py 2 | prune testing/benchmarks 3 | exclude testing/bench*.py 4 | recursive-include include *.h 5 | include LICENSE 6 | include mariadb_windows.py 7 | include mariadb_posix.py 8 | include MANIFEST.in 9 | include MANIFEST 10 | include README.md 11 | include site.cfg 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # MariaDB Connector/Python 8 | 9 | [![License (LGPL version 2.1)][licence-image]](LICENSE) 10 | [![Python 3.7][python-image]][python-url] 11 | [![Build Status](https://travis-ci.com/mariadb-corporation/mariadb-connector-python.svg?branch=1.1)](https://app.travis-ci.com/mariadb-corporation/mariadb-connector-python) 12 | 13 | Coverity Scan Build Status 15 | 16 | 17 | MariaDB Connector/Python enables python programs to access MariaDB and MySQL databases, using an API 18 | which is compliant with the Python DB API 2.0 (PEP-249). It is written in C and uses MariaDB Connector/C 19 | client library for client server communication. 20 | 21 | ## License 22 | 23 | MariaDB Connector/Python is licensed under the LGPL 2.1 or later (LGPL-2.1-or-later) 24 | 25 | ## Source code 26 | 27 | MariaDB Connector/Python source code is hosted on [Github](https://github.com/mariadb-corporation/mariadb-connector-python) 28 | 29 | ## Documentation 30 | 31 | MariaDB Connector/Python documentation can be found on [Github Pages](https://mariadb-corporation.github.io/mariadb-connector-python/) 32 | 33 | ## Bugs 34 | 35 | Bugs and feature requests should be filed in the [MariaDB bug ticket system](https://jira.mariadb.org/) 36 | 37 | 38 | [licence-image]:https://img.shields.io/badge/license-GNU%20LGPL%20version%202.1-green.svg?style=flat-square 39 | [python-image]:https://img.shields.io/badge/python-3.7-blue.svg 40 | [python-url]:https://www.python.org/downloads/release/python-370/ 41 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /docs/examples/basic01.py: -------------------------------------------------------------------------------- 1 | # Import MariaDB Connector/Python module 2 | import mariadb 3 | 4 | # connection parameters 5 | conn_params= { 6 | "user" : "example_user", 7 | "password" : "GHbe_Su3B8", 8 | "host" : "localhost", 9 | "database" : "test" 10 | } 11 | 12 | # Establish a connection 13 | connection= mariadb.connect(**conn_params) 14 | 15 | cursor= connection.cursor() 16 | 17 | # Create a database table 18 | cursor.execute("DROP TABLE IF EXISTS mytest") 19 | cursor.execute("CREATE TABLE mytest(id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY," 20 | "first_name VARCHAR(100), last_name VARCHAR(100))") 21 | 22 | 23 | # Populate table with some data 24 | cursor.execute("INSERT INTO mytest(first_name, last_name) VALUES (?,?)", 25 | ("Robert", "Redford")) 26 | 27 | # retrieve data 28 | cursor.execute("SELECT id, first_name, last_name FROM mytest") 29 | 30 | # print content 31 | row= cursor.fetchone() 32 | print(*row, sep='\t') 33 | 34 | # free resources 35 | cursor.close() 36 | connection.close() 37 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | myst-parser 3 | sphinx-markdown-builder 4 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | ------------- 2 | API Reference 3 | ------------- 4 | 5 | .. sectionauthor:: Georg Richter 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :caption: Contents: 10 | 11 | module 12 | connection 13 | cursor 14 | pool 15 | constants 16 | 17 | -------------------------------------------------------------------------------- /docs/source/bugs.rst: -------------------------------------------------------------------------------- 1 | Bug reports 2 | =========== 3 | 4 | If you think that you have found a bug in MariaDB Software, please report it at 5 | `Jira issue tracker `_ and file it under Project CONPY (abbreviation for Connector/Python). 6 | 7 | How to report a bug? 8 | -------------------- 9 | 10 | Search first 11 | ^^^^^^^^^^^^ 12 | Always search the bug database first. Especially if you are using an older version of MariaDB Connector/Python it could 13 | be reported already by someone else or it was already fixed in a more recent version. 14 | 15 | What? 16 | ^^^^^ 17 | We need to know what you did, what happened and what you wanted to happen. A report stating that method xyz() hangs, will 18 | not allow us to provide you with an advice or fix, since we just don't know what the method is doing. 19 | Beside versions a good bug report contains a short script which reproduces the problem. Sometimes it is also necessary to 20 | provide the definition (and data) of used tables. 21 | 22 | Versions of components 23 | ^^^^^^^^^^^^^^^^^^^^^^ 24 | MariaDB Connector/Python interacts with two other components: The database server and MariaDB Connector/C. Latter one is responsible for client/server communication. An error does not necessarily have to exist in Connector / Python; it can also be an error in the database server or in Connector/C. In this case we will reclassify the bug (MDEV or CONC). 25 | 26 | Avoid screenshots! 27 | ^^^^^^^^^^^^^^^^^^ 28 | Use copy and paste instead. Screenshots create a lot more data volume and are often difficult to 29 | read on mobile devices. Typing program code from a screenshot is also an unnecessary effort. 30 | 31 | Keep it simple! 32 | ^^^^^^^^^^^^^^^ 33 | Scripts which are longer than 10 lines often contain code which is not relevant to the problem and increases 34 | the time to figure out the real problem. So try to keep it simple and focus on the real problem. 35 | 36 | The sane applies for database related components like tables, views and stored procedures. Avoid table definitions with 37 | hundred of columns if the problem can be reproduced with only 4 columns, 38 | 39 | Only report one problem in one bug report 40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 41 | If you have encountered two or more bugs which are not related, please file an issue for each of them. 42 | 43 | Crashes 44 | ^^^^^^^ 45 | If your application crashes, please also provide if possible a backtrace and output of the exception. 46 | 47 | Report bugs in English only! 48 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 49 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | import mariadb 16 | from typing import Sequence 17 | from datetime import datetime 18 | print(mariadb.__path__) 19 | sys.path.insert(0, os.path.abspath('../..')) 20 | sys.setrecursionlimit(1500) 21 | 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'MariaDB Connector/Python' 26 | copyright = '2019-%s MariaDB Corporation and Georg Richter' % datetime.now().year 27 | author = 'Georg Richter' 28 | 29 | # The full version, including alpha/beta/rc tags 30 | release = mariadb.__version__ 31 | if len(mariadb.__version_info__) > 3: 32 | release= release + "-" + mariadb.__version_info__[3] 33 | add_module_names= False 34 | 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = ['sphinx.ext.doctest', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 42 | 'sphinx.ext.extlinks', 'myst_parser', 'sphinx_markdown_builder' ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = [] 51 | 52 | pygments_style = 'sphinx' 53 | 54 | master_doc = 'index' 55 | 56 | # Enable Markdown support via MyST-Parser 57 | source_suffix = { 58 | '.rst': 'restructuredtext', 59 | '.md': 'markdown', 60 | } 61 | 62 | # Optional: Enable MyST extensions (customize as needed) 63 | myst_enable_extensions = [ 64 | "colon_fence", 65 | "deflist", 66 | "html_admonition", 67 | "html_image", 68 | "linkify", 69 | "substitution", 70 | "tasklist", 71 | ] 72 | 73 | 74 | # -- Options for HTML output ------------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. See the documentation for 77 | # a list of builtin themes. 78 | # 79 | html_theme = 'classic' 80 | 81 | # Add any paths that contain custom static files (such as style sheets) here, 82 | # relative to this directory. They are copied after the builtin static files, 83 | # so a file named "default.css" will overwrite the builtin "default.css". 84 | html_static_path = ['_static'] 85 | html_show_sourcelink = False 86 | 87 | highlight_language = 'python' 88 | 89 | rst_epilog=""" 90 | .. |MCP| replace:: MariaDB Connector/Python 91 | .. |MCC| replace:: MariaDB Connector/C 92 | .. |MCC_minversion| replace:: 3.3.1 93 | .. |DBAPI| replace:: DB API 2.0 (:PEP:`249`) 94 | .. |MCDP| replace:: `MariaDB Connector Download page `__ 95 | """ 96 | 97 | extlinks= { 98 | 'conpy' : ('https://jira.mariadb.org/browse/CONPY-%s', 'CONPY-%s'), 99 | 'PEP' : ('https://peps.python.org/pep-%s', 'PEP-%s') 100 | } 101 | -------------------------------------------------------------------------------- /docs/source/connection.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | The connection class 3 | ==================== 4 | 5 | .. sectionauthor:: Georg Richter 6 | 7 | .. autoclass:: mariadb.connections.Connection 8 | 9 | ----------------------- 10 | Connection constructors 11 | ----------------------- 12 | 13 | .. automethod:: mariadb.connections.Connection.cursor 14 | 15 | .. versionadded:: 1.0.1 16 | .. automethod:: mariadb.connections.Connection.xid 17 | 18 | ------------------ 19 | Connection methods 20 | ------------------ 21 | 22 | .. versionadded:: 1.1.0 23 | .. automethod:: mariadb.connections.Connection.begin 24 | 25 | .. automethod:: mariadb.connections.Connection.commit 26 | 27 | .. automethod:: mariadb.connections.Connection.change_user(user, password, database) 28 | 29 | .. automethod:: mariadb.connections.Connection.close 30 | 31 | .. automethod:: mariadb.connections.Connection.cursor 32 | 33 | .. versionadded:: 1.1.2 34 | .. automethod:: mariadb.connections.Connection.dump_debug_info 35 | 36 | .. automethod:: mariadb.connections.Connection.get_server_version 37 | 38 | .. versionadded:: 1.0.5 39 | .. automethod:: mariadb.connections.Connection.escape_string 40 | 41 | .. testcode:: 42 | 43 | import mariadb 44 | 45 | # connection parameters 46 | conn_params= { 47 | "user" : "example_user", 48 | "password" : "GHbe_Su3B8", 49 | "host" : "localhost" 50 | } 51 | 52 | # Establish a connection 53 | connection= mariadb.connect(**conn_params) 54 | 55 | string= 'This string contains the following special characters: \,"' 56 | print(connection.escape_string(string)) 57 | 58 | *Output*: 59 | 60 | .. testoutput:: 61 | 62 | This string contains the following special characters: \\,\" 63 | 64 | 65 | .. automethod:: mariadb.connections.Connection.kill 66 | 67 | .. note:: 68 | A thread_id from other connections can be determined by executing the SQL statement ``SHOW PROCESSLIST`` 69 | The thread_id of the current connection the current connection is stored in :data:`connection_id` attribute. 70 | 71 | .. automethod:: mariadb.connections.Connection.ping() 72 | 73 | .. automethod:: mariadb.connections.Connection.reconnect 74 | 75 | .. automethod:: mariadb.connections.Connection.reset 76 | 77 | .. automethod:: mariadb.connections.Connection.rollback() 78 | 79 | .. versionadded:: 1.1.0 80 | .. automethod:: mariadb.connections.Connection.select_db 81 | 82 | .. automethod:: mariadb.connections.Connection.show_warnings 83 | 84 | .. automethod:: mariadb.connections.Connection.tpc_begin 85 | 86 | .. automethod:: mariadb.connections.Connection.tpc_commit 87 | 88 | .. automethod:: mariadb.connections.Connection.tpc_prepare 89 | 90 | .. automethod:: mariadb.connections.Connection.tpc_recover 91 | 92 | .. automethod:: mariadb.connections.Connection.tpc_rollback 93 | 94 | --------------------- 95 | Connection attributes 96 | --------------------- 97 | 98 | .. autoattribute:: mariadb.connections.Connection.auto_reconnect 99 | 100 | .. autoattribute:: mariadb.connections.Connection.autocommit 101 | 102 | .. autoattribute:: mariadb.connections.Connection.character_set 103 | 104 | .. versionadded:: 1.1.0: 105 | .. autoattribute:: mariadb.connections.Connection.client_capabilities 106 | 107 | .. autoattribute:: mariadb.connections.Connection.collation 108 | 109 | .. autoattribute:: mariadb.connections.Connection.connection_id 110 | 111 | .. autoattribute:: mariadb.connections.Connection.database 112 | 113 | .. versionadded:: 1.1.0 114 | .. autoattribute:: mariadb.connections.Connection.open 115 | 116 | .. versionadded:: 1.1.0 117 | .. autoattribute:: mariadb.connections.Connection.server_capabilities 118 | 119 | .. versionadded:: 1.1.0 120 | .. autoattribute:: mariadb.connections.Connection.extended_server_capabilities 121 | 122 | .. autoattribute:: mariadb.connections.Connection.server_info 123 | 124 | .. autoattribute:: mariadb.connections.Connection.server_name 125 | 126 | .. autoattribute:: mariadb.connections.Connection.server_port 127 | 128 | .. versionadded:: 1.1.0 129 | .. autoattribute:: mariadb.connections.Connection.server_status 130 | 131 | .. autoattribute:: mariadb.connections.Connection.server_version 132 | 133 | .. autoattribute:: mariadb.connections.Connection.server_version_info 134 | 135 | .. versionadded:: 1.0.5 136 | .. autoattribute:: mariadb.connections.Connection.tls_cipher 137 | 138 | .. autoattribute:: mariadb.connections.Connection.tls_version 139 | 140 | .. versionadded:: 1.1.11 141 | .. autoattribute:: mariadb.connections.Connection.tls_peer_cert_info 142 | 143 | .. autoattribute:: mariadb.connections.Connection.unix_socket 144 | 145 | .. autoattribute:: mariadb.connections.Connection.user 146 | 147 | .. autoattribute:: mariadb.connections.Connection.warnings 148 | -------------------------------------------------------------------------------- /docs/source/constants.rst: -------------------------------------------------------------------------------- 1 | Constants 2 | ==================== 3 | 4 | Constants are declared in mariadb.constants module. 5 | 6 | For using constants of various types they have to be imported first: 7 | 8 | .. code-block:: python 9 | 10 | from mariadb.constants import * 11 | 12 | ---------- 13 | CAPABILITY 14 | ---------- 15 | 16 | .. versionadded:: 1.1.4 17 | 18 | .. automodule:: mariadb.constants.CAPABILITY 19 | 20 | .. testcode:: 21 | 22 | import mariadb 23 | from mariadb.constants import * 24 | 25 | # connection parameters 26 | conn_params= { 27 | "user" : "example_user", 28 | "password" : "GHbe_Su3B8", 29 | "host" : "localhost" 30 | } 31 | 32 | # Establish a connection 33 | connection= mariadb.connect(**conn_params) 34 | 35 | # test if LOAD DATA LOCAL INFILE is supported 36 | if connection.server_capabilities & CAPABILITY.LOCAL_FILES: 37 | print("Server supports LOCAL INFILE") 38 | 39 | *Output*: 40 | 41 | .. testoutput:: 42 | 43 | Server supports LOCAL INFILE 44 | 45 | 46 | -------------- 47 | CLIENT 48 | -------------- 49 | 50 | .. versionadded:: 1.1.0 51 | 52 | .. automodule:: mariadb.constants.CLIENT 53 | 54 | .. deprecated:: 1.1.4 55 | Use CAPABILITY constants instead 56 | 57 | -------------- 58 | CURSOR 59 | -------------- 60 | 61 | .. versionadded:: 1.1.0 62 | 63 | .. automodule:: mariadb.constants.CURSOR 64 | 65 | .. py:data:: CURSOR.NONE 66 | 67 | This is the default setting (no cursor) 68 | 69 | .. py:data:: CURSOR.READ_ONLY 70 | 71 | Will create a server side read only cursor. The cursor is a forward cursor, which 72 | means it is not possible to scroll back. 73 | 74 | -------------- 75 | ERR (Error) 76 | -------------- 77 | 78 | .. versionadded:: 1.1.2 79 | 80 | 81 | Using ERR constants instead of error numbers make the code more readable. Error constants 82 | are defined in constants.ERR module 83 | 84 | .. testcode:: 85 | 86 | import mariadb 87 | from mariadb.constants import * 88 | 89 | # connection parameters 90 | conn_params= { 91 | "user" : "example_user", 92 | "password" : "wrong_password", 93 | "host" : "localhost" 94 | } 95 | 96 | # try to establish a connection 97 | try: 98 | connection= mariadb.connect(**conn_params) 99 | except mariadb.OperationalError as Err: 100 | if Err.errno == ERR.ER_ACCESS_DENIED_ERROR: 101 | print("Access denied. Wrong password!") 102 | 103 | *Output*: 104 | 105 | .. testoutput:: 106 | 107 | Access denied. Wrong password! 108 | 109 | -------------- 110 | FIELD_FLAG 111 | -------------- 112 | 113 | .. versionadded:: 1.1.0 114 | 115 | .. automodule:: mariadb.constants.FIELD_FLAG 116 | 117 | 118 | .. py:data:: FIELD_FLAG.NOT_NULL 119 | 120 | column is defined as not NULL 121 | 122 | .. py:data:: FIELD_FLAG.PRIMARY_KEY 123 | 124 | column is (part of) a primary key 125 | 126 | .. py:data:: FIELD_FLAG.UNIQUE_KEY 127 | 128 | column is (part of) a unique key 129 | 130 | .. py:data:: FIELD_FLAG.MULTIPLE_KEY 131 | 132 | column is (part of) a key 133 | 134 | .. py:data:: FIELD_FLAG.BLOB 135 | 136 | column contains a binary object 137 | 138 | .. py:data:: FIELD_FLAG.UNSIGNED 139 | 140 | numeric column is defined as unsigned 141 | 142 | .. py:data:: FIELD_FLAG.ZEROFILL 143 | 144 | column has zerofill attribute 145 | 146 | .. py:data:: FIELD_FLAG.BINARY 147 | 148 | column is a binary 149 | 150 | .. py:data:: FIELD_FLAG.ENUM 151 | 152 | column is defined as enum 153 | 154 | .. py:data:: FIELD_FLAG.AUTO_INCREMENT 155 | 156 | column is an auto_increment column 157 | 158 | .. py:data:: FIELD_FLAG.TIMESTAMP 159 | 160 | column is defined as time stamp 161 | 162 | .. py:data:: FIELD_FLAG.SET 163 | 164 | column is defined as SET 165 | 166 | .. py:data:: FIELD_FLAG.NO_DEFAULT 167 | 168 | column hasn't a default value 169 | 170 | .. py:data:: FIELD_FLAG.ON_UPDATE_NOW 171 | 172 | column will be set to current timestamp on UPDATE 173 | 174 | .. py:data:: FIELD_FLAG.NUMERIC 175 | 176 | column contains numeric value 177 | 178 | .. py:data:: FIELD_FLAG.PART_OF_KEY 179 | 180 | column is part of a key 181 | 182 | ---------- 183 | FIELD_TYPE 184 | ---------- 185 | 186 | .. automodule:: mariadb.constants.FIELD_TYPE 187 | 188 | .. py:data:: FIELD_TYPE.TINY 189 | 190 | column type is TINYINT (1-byte integer) 191 | 192 | .. py:data:: FIELD_TYPE.SHORT 193 | 194 | column type is SMALLINT (2-byte integer) 195 | 196 | .. py:data:: FIELD_TYPE.LONG 197 | 198 | column tyoe is INT (4-byte integer) 199 | 200 | .. py:data:: FIELD_TYPE.FLOAT 201 | 202 | column type is FLOAT (4-byte single precision) 203 | 204 | .. py:data:: FIELD_TYPE.DOUBLE 205 | 206 | column type is DOUBLE (8-byte double precision) 207 | 208 | .. py:data:: FIELD_TYPE.NULL 209 | 210 | column type is NULL 211 | 212 | .. py:data:: FIELD_TYPE.TIMESTAMP 213 | 214 | column tyoe is TIMESTAMP 215 | 216 | .. py:data:: FIELD_TYPE.LONGLONG 217 | 218 | column tyoe is BIGINT (8-byte Integer) 219 | 220 | .. py:data:: FIELD_TYPE.INT24 221 | 222 | column type is MEDIUMINT (3-byte Integer) 223 | 224 | .. py:data:: FIELD_TYPE.DATE 225 | 226 | column type is DATE 227 | 228 | .. py:data:: FIELD_TYPE.TIME 229 | 230 | column type is TIME 231 | 232 | .. py:data:: FIELD_TYPE.DATETIME 233 | 234 | column type is YEAR 235 | 236 | .. py:data:: FIELD_TYPE.YEAR 237 | 238 | .. py:data:: FIELD_TYPE.VARCHAR 239 | 240 | column type is YEAR 241 | 242 | .. py:data:: FIELD_TYPE.BIT 243 | 244 | column type is BIT 245 | 246 | .. py:data:: FIELD_TYPE.JSON 247 | 248 | column type is JSON 249 | 250 | .. py:data:: FIELD_TYPE.NEWDECIMAL 251 | 252 | column type is DECIMAL 253 | 254 | .. py:data:: FIELD_TYPE.ENUM 255 | 256 | column type is ENUM 257 | 258 | .. py:data:: FIELD_TYPE.SET 259 | 260 | column type is SET 261 | 262 | .. py:data:: FIELD_TYPE.TINY_BLOB 263 | 264 | column type is TINYBLOB (max. length of 255 bytes) 265 | 266 | .. py:data:: FIELD_TYPE.MEDIUM_BLOB 267 | 268 | column type is MEDIUMBLOB (max. length of 16,777,215 bytes) 269 | 270 | .. py:data:: FIELD_TYPE.LONG_BLOB 271 | 272 | column type is LONGBLOB (max. length 4GB bytes) 273 | 274 | .. py:data:: FIELD_TYPE.BLOB 275 | 276 | column type is BLOB (max. length of 65.535 bytes) 277 | 278 | .. py:data:: FIELD_TYPE.VAR_STRING 279 | 280 | column type is VARCHAR (variable length) 281 | 282 | .. py:data:: FIELD_TYPE.STRING 283 | 284 | column tyoe is CHAR (fixed length) 285 | 286 | .. py:data:: FIELD_TYPE.GEOMETRY 287 | 288 | column type is GEOMETRY 289 | 290 | -------------- 291 | INDICATORS 292 | -------------- 293 | 294 | Indicator values are used in executemany() method of cursor class to 295 | indicate special values when connected to a MariaDB server 10.2 or newer. 296 | 297 | .. py:data:: INDICATOR.NULL 298 | 299 | indicates a NULL value 300 | 301 | .. py:data:: INDICATOR.DEFAULT 302 | 303 | indicates to use default value of column 304 | 305 | .. py:data:: INDICATOR.IGNORE 306 | 307 | indicates to ignore value for column for UPDATE statements. 308 | If set, the column will not be updated. 309 | 310 | .. py:data:: INDICATOR.IGNORE_ROW 311 | 312 | indicates not to update the entire row. 313 | 314 | --------------- 315 | INFO 316 | --------------- 317 | 318 | .. versionadded:: 1.1.0 319 | 320 | For internal use only 321 | 322 | --------------- 323 | TPC_STATE 324 | --------------- 325 | 326 | .. versionadded:: 1.1.0 327 | 328 | For internal use only 329 | 330 | --------------- 331 | STATUS 332 | --------------- 333 | .. versionadded:: 1.1.0 334 | 335 | The STATUS constants are used to check the server status of 336 | the current connection. 337 | 338 | Example: 339 | 340 | .. code-block:: python 341 | 342 | cursor.callproc("my_storedprocedure", (1,"foo")) 343 | 344 | if (connection.server_status & STATUS.SP_OUT_PARAMS): 345 | print("retrieving output parameters from store procedure") 346 | ... 347 | else: 348 | print("retrieving data from stored procedure") 349 | .... 350 | 351 | 352 | .. py:data:: STATUS.IN_TRANS 353 | 354 | Pending transaction 355 | 356 | .. py:data:: STATUS.AUTOCOMMIT 357 | 358 | Server operates in autocommit mode 359 | 360 | .. py:data:: STATUS.MORE_RESULTS_EXIST 361 | 362 | The result from last executed statement contained two or more result 363 | sets which can be retrieved by cursors nextset() method. 364 | 365 | .. py:data:: STATUS.QUERY_NO_GOOD_INDEX_USED 366 | 367 | The last executed statement didn't use a good index. 368 | 369 | .. py:data:: STATUS.QUERY_NO_INDEX_USED 370 | 371 | The last executed statement didn't use an index. 372 | 373 | .. py:data:: STATUS.CURSOR_EXISTS 374 | 375 | The last executed statement opened a server side cursor. 376 | 377 | .. py:data:: STATUS.LAST_ROW_SENT 378 | 379 | For server side cursors this flag indicates end of a result set. 380 | 381 | .. py:data:: STATUS.DB_DROPPED 382 | 383 | The current database in use was dropped and there is no default 384 | database for the connection anymore. 385 | 386 | .. py:data:: STATUS.NO_BACKSLASH_ESCAPES 387 | 388 | Indicates that SQL mode NO_BACKSLASH_ESCAPE is active, which means 389 | that the backslash character '\' becomes an ordinary character. 390 | 391 | .. py:data:: STATUS.QUERY_WAS_SLOW 392 | 393 | The previously executed statement was slow (and needs to be optimized). 394 | 395 | .. py:data:: STATUS.PS_OUT_PARAMS 396 | 397 | The current result set contains output parameters of a stored procedure. 398 | 399 | .. py:data:: STATUS.SESSION_STATE_CHANGED 400 | 401 | The session status has been changed. 402 | 403 | .. py:data:: STATUS.ANSI_QUOTES 404 | 405 | SQL mode ANSI_QUOTES is active, 406 | -------------------------------------------------------------------------------- /docs/source/cursor.rst: -------------------------------------------------------------------------------- 1 | The cursor class 2 | ==================== 3 | .. sectionauthor:: Georg Richter 4 | 5 | .. autoclass:: mariadb.cursors.Cursor 6 | 7 | -------------- 8 | Cursor methods 9 | -------------- 10 | 11 | .. automethod:: mariadb.cursors.Cursor.callproc 12 | 13 | Example: 14 | 15 | .. code-block:: python 16 | 17 | >>>cursor.execute("CREATE PROCEDURE p1(IN i1 VAR CHAR(20), OUT o2 VARCHAR(40))" 18 | "BEGIN" 19 | " SELECT 'hello'" 20 | " o2:= 'test'" 21 | "END") 22 | >>>cursor.callproc('p1', ('foo', 0)) 23 | >>> cursor.sp_outparams 24 | False 25 | >>> cursor.fetchone() 26 | ('hello',) 27 | >>> cursor.nextset() 28 | True 29 | >>> cursor.sp_outparams 30 | True 31 | >>> cursor.fetchone() 32 | ('test',) 33 | 34 | .. automethod:: mariadb.cursors.Cursor.execute 35 | 36 | .. automethod:: mariadb.cursors.Cursor.executemany 37 | 38 | Example: 39 | 40 | The following example will insert 3 rows: 41 | 42 | .. code-block:: python 43 | 44 | data= [ 45 | (1, 'Michael', 'Widenius') 46 | (2, 'Diego', 'Dupin') 47 | (3, 'Lawrin', 'Novitsky') 48 | ] 49 | cursor.executemany("INSERT INTO colleagues VALUES (?, ?, ?)", data) 50 | 51 | To insert special values like NULL or a column default, you need to specify indicators: 52 | 53 | - INDICATOR.NULL is used for NULL values 54 | - INDICATOR.IGNORE is used to skip update of a column. 55 | - INDICATOR.DEFAULT is used for a default value (insert/update) 56 | - INDICATOR.ROW is used to skip update/insert of the entire row. 57 | 58 | .. note:: 59 | 60 | - All values for a column must have the same data type. 61 | - Indicators can only be used when connecting to a MariaDB Server 10.2 or newer. MySQL servers don't support this feature. 62 | 63 | 64 | .. automethod:: mariadb.cursors.Cursor.fetchall 65 | 66 | .. automethod:: mariadb.cursors.Cursor.fetchmany 67 | 68 | .. automethod:: mariadb.cursors.Cursor.fetchone 69 | 70 | .. automethod:: mariadb.cursors.Cursor.next 71 | 72 | .. automethod:: mariadb.cursors.Cursor.nextset 73 | 74 | .. automethod:: mariadb.cursors.Cursor.scroll 75 | 76 | .. automethod:: mariadb.cursors.Cursor.setinputsizes() 77 | 78 | .. automethod:: mariadb.cursors.Cursor.setoutputsize() 79 | 80 | ----------------- 81 | Cursor attributes 82 | ----------------- 83 | 84 | .. autoattribute:: mariadb.cursors.Cursor.arraysize 85 | 86 | This read/write attribute specifies the number of rows to fetch at a time with .fetchmany(). It defaults to 1 meaning to fetch a single row at a time 87 | 88 | .. autoattribute:: mariadb.cursors.Cursor.buffered 89 | 90 | .. autoattribute:: mariadb.cursors.Cursor.closed 91 | 92 | .. autoattribute:: mariadb.cursors.Cursor.connection 93 | 94 | .. autoattribute:: mariadb.cursors.Cursor.description 95 | 96 | .. note:: 97 | 98 | The 8th parameter 'field_flags' is an extension to the PEP-249 DB API standard. 99 | In combination with the type element field, it can be determined for example, 100 | whether a column is a BLOB or TEXT field: 101 | 102 | .. versionadded:: 1.1.0 103 | The parameter table_name, original_column_name and original_table_name are an 104 | extension to the PEP-249 DB API standard. 105 | 106 | .. code-block:: python 107 | 108 | if cursor.description[0][1] == FIELD_TYPE.BLOB: 109 | if cursor.description[0][7] == FIELD_FLAG.BINARY: 110 | print("column is BLOB") 111 | else: 112 | print("column is TEXT") 113 | 114 | 115 | .. autoattribute:: mariadb.cursors.Cursor.lastrowid 116 | 117 | .. versionadded:: 1.1.8 118 | 119 | .. autoattribute:: mariadb.cursors.Cursor.metadata 120 | 121 | .. autoattribute:: mariadb.cursors.Cursor.sp_outparams 122 | 123 | 124 | .. versionadded:: 1.1.0 125 | 126 | .. autoattribute:: mariadb.cursors.Cursor.paramcount 127 | 128 | .. autoattribute:: mariadb.cursors.Cursor.rowcount 129 | 130 | .. note:: 131 | 132 | For unbuffered cursors (default) the exact number of rows can only be 133 | determined after all rows were fetched. 134 | 135 | Example: 136 | 137 | .. code-block:: python 138 | 139 | >>> cursor=conn.cursor() 140 | >>> cursor.execute("SELECT 1") 141 | >>> cursor.rowcount 142 | -1 143 | >>> rows= cursor.fetchall() 144 | >>> cursor.rowcount 145 | 1 146 | >>> cursor=conn.cursor(buffered=True) 147 | >>> cursor.execute("SELECT 1") 148 | >>> cursor.rowcount 149 | 1 150 | 151 | .. autoattribute:: mariadb.cursors.Cursor.statement 152 | 153 | .. autoattribute:: mariadb.cursors.Cursor.warnings 154 | 155 | .. note:: 156 | 157 | Warnings can be retrieved by the show_warnings() method of connection class. 158 | -------------------------------------------------------------------------------- /docs/source/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | |MCP| FAQ 4 | ========= 5 | 6 | This is a list of frequently asked questions about |MCP|. Feel free to suggest new 7 | entries! 8 | 9 | .. _installation_faq: 10 | 11 | Installation 12 | ^^^^^^^^^^^^ 13 | 14 | .. collapse:: Error: "Python.h: No such file or directory" 15 | 16 | The header files and libraries of the Python development package weren't properly installed. 17 | Use your package manager to install them system-wide: 18 | 19 | .. collapse:: Alpine (using apk): 20 | 21 | .. code-block:: console 22 | 23 | sudo apk add python3-dev 24 | 25 | .. collapse:: Ubuntu/Debian (using apt): 26 | 27 | .. code-block:: console 28 | 29 | sudo apt-get install python3-dev 30 | 31 | .. collapse:: CentOS/RHEL (using yum): 32 | 33 | .. code-block:: console 34 | 35 | sudo yum install python3-devel 36 | 37 | .. collapse:: Fedora (using dnf): 38 | 39 | .. code-block:: console 40 | 41 | sudo dnf install python3-devel 42 | 43 | .. collapse:: MacOSX (using homebrew) 44 | 45 | .. code-block:: console 46 | 47 | brew install mariadb-connector-c 48 | 49 | .. collapse:: OpenSuse (using zypper): 50 | 51 | .. code-block:: console 52 | 53 | sudo zypper in python3-devel 54 | 55 | Note: The python3 development packages of your distribution might not cover all minor versions 56 | of python3. If you are using python3.10 you may need install python3.10-dev. 57 | 58 | 59 | .. collapse:: ModuleNotFoundError: No module named 'packaging' 60 | 61 | With deprecation of distutils (see :PEP:`632`) version functions of distutils module were 62 | replaced in |MCP| 1.1.5 by packaging version functions. 63 | 64 | Before you can install |MCP| you have to install the packaging module: 65 | 66 | .. code-block:: console 67 | 68 | pip3 install packaging 69 | 70 | .. collapse:: MariaDB Connector/Python requires MariaDB Connector/C >= 3.3.1, found version 3.1.2 71 | 72 | The previously installed version of |MCC| is too old and cannot be used for the |MCP| version 73 | you are trying to install. 74 | 75 | To determine the installed version of |MCC|, execute the command 76 | 77 | .. code-block:: console 78 | 79 | mariadb_config --cc_version 80 | 81 | - Check if your distribution can be upgraded to a more recent version of |MCC|, which fits the requirements. 82 | - If your distribution doesn't provide a recent version of |MCC|, check the |MCDP|, which provides 83 | latest versions for the major distributions. 84 | - If none of the above will work for you, build and install |MCC| from source. 85 | 86 | .. collapse:: OSError: mariadb_config not found. 87 | 88 | The mariadb_config program is used to retrieve configuration information (such as the location of 89 | header files and libraries, installed version, ..) from |MCC| 90 | 91 | This error indicates that |MCC|, an important dependency for client/server communication that needs 92 | to be preinstalled, either was not installed or could not be found. 93 | 94 | * If |MCC| was previously installed, the installation script cannot detect the location of mariadb_config. 95 | Locate the directory where mariadb_config was installed and add this directory to your PATH. 96 | 97 | .. code-block:: console 98 | 99 | # locate mariadb_config 100 | sudo find / -name "mariadb_config" 101 | 102 | * If |MCC| was not installed and the location of mariadb_config couldn't be detected, please install 103 | MariaDB Connector/C. 104 | 105 | .. collapse:: Error: struct st_mariadb_methods’ has no member named ‘db_execute_generate_request’ 106 | 107 | Even if the correct version of |MCC| was installed, there are multiple mysql.h include files installed 108 | on your system, either from libmysql or an older |MCC| installation. This can be checked by executing 109 | 110 | .. code-block:: console 111 | 112 | export CFLAGS="-V -E" 113 | pip3 install mariadb > output.txt 114 | 115 | Open output.txt in your favourite editor and search for "search starts here" where you can see the include 116 | files and paths used for the build. 117 | 118 | .. collapse:: Q: My distribution doesn't provide a recent version of MariaDB Connector/C 119 | 120 | If you distribution doesn't provide a recent version of |MCC| (required version is |MCC_minversion| ) you either 121 | can download a version of |MCC| from the |MCDP| or build the package from source: 122 | 123 | .. code-block:: console 124 | 125 | mkdir bld 126 | cd bld 127 | cmake .. 128 | make 129 | make install 130 | 131 | 132 | .. collapse:: Q: Does MariaDB Connector/Python provide pre-releases or snapshot builds which contain recent bug fixes? 133 | 134 | No. If an issue was fixed, the fix will be available in the next release via Python's package 135 | manager repository (pypi.org). 136 | 137 | .. collapse:: Q: How can I build an actual version from github sources? 138 | 139 | To build |MCP| from github sources, checkout latest sources from github 140 | 141 | .. code-block:: console 142 | 143 | git clone https://github.com/mariadb-corporation/mariadb-conector-pyhon.git 144 | 145 | and build and install it with 146 | 147 | .. code-block:: console 148 | 149 | python3 setup.py build 150 | python3 -m pip install . 151 | 152 | 153 | Connecting 154 | ^^^^^^^^^^ 155 | 156 | .. collapse:: mariadb.OperationalError: Can't connect to local server through socket '/tmp/mysql.sock' 157 | 158 | 1. Check if MariaDB server has been started. 159 | 160 | 2. Check if the MariaDB server was correctly configured and uses the right socket file: 161 | 162 | .. code-block:: console 163 | 164 | mysqld --help --verbose | grep socket 165 | 166 | If the socket is different and cannot be changed, you can specify the socket in your 167 | connection parameters. 168 | 169 | .. code-block:: python 170 | 171 | connection= mariab.connect(unix_socket="/path_socket/mysql.sock", ....) 172 | 173 | Another option is setting the environment variable MYSQL_UNIX_PORT. 174 | 175 | .. code-block:: console 176 | 177 | export MYSQL_UNIX_PORT=/path_to/mysql.sock 178 | 179 | .. collapse:: Q: Which authentication methods are supported by MariaDB Connector/Python? 180 | 181 | |MCP| uses |MCC| for client-server communication. That means all authenticatoin plugins shipped 182 | together with |MCC| can be used for user authentication. 183 | 184 | 185 | General: 186 | ^^^^^^^^ 187 | 188 | .. collapse:: Q: How do I execute multipe statements with cursor.execute() ? 189 | 190 | Since |MCP| uses binary protocol for client-server communication, this feature is not supported yet. 191 | 192 | .. collapse:: Q: Does MariaDB Connector/Python works with Python 2.x ? 193 | 194 | Python versions which reached their end of life are not officially supported. While |MCP| might still work 195 | with older Python 3.x versions, it doesn't work with Python version 2.x. 196 | 197 | .. collapse:: Q: How can I see a transformed statement? Is there a mogrify() method available? 198 | 199 | No, |MCP| Python uses binary protocol for client/server communication. Before a statement will be executed 200 | it will be parsed and parameter markers which are different than question marks will be replaced by question 201 | marks. Afterwards the statement will be sent together with data to the server. The transformed statement can 202 | be obtained by cursor.statement attribute 203 | 204 | Example: 205 | 206 | .. code-block:: python 207 | 208 | data = ("Future", 2000) 209 | statement = """SELECT DATE_FORMAT(creation_time, '%h:%m:%s') as time, topic, amount 210 | FROM mytable WHERE topic=%s and id > %s""" 211 | cursor.execute(statement, data) 212 | print(cursor.statement) 213 | 214 | .. code-block:: console 215 | 216 | SELECT DATE_FORMAT(creation_time, '%h:%m:%s') as time, topic, amount FROM mytable WHERE topic=? and id > ? 217 | 218 | Please note, that there is no need to escape '%s' by '%%s' for the time conversion in DATE_FORMAT() function. 219 | 220 | .. collapse:: Q: Does MariaDB Connector/Python supports paramstyle "pyformat" ? 221 | 222 | The default paramstyle (see :PEP:`249`) is **qmark** (question mark) for parameter markers. For compatibility 223 | with other drivers |MCP| also supports (and automatically recognizes) the **format** and **pyformat** parameter 224 | styles. 225 | 226 | Mixing different paramstyles within the same query is not supported and will raise an exception. 227 | 228 | 229 | 230 | Transactions 231 | ^^^^^^^^^^^^ 232 | 233 | .. collapse:: Q: Previously inserted records disappeared after my program finished. 234 | 235 | Default for autocommit in |MCP| is off, which means every transaction must be committed. 236 | Uncommitted pending transactions are rolled back automatically when the connection is closed. 237 | 238 | .. code-block:: python 239 | 240 | cursor= connection.cursor() 241 | cursor.execute("CREATE TABLE t1 (id int, name varchar(20))") 242 | 243 | #insert 244 | data= [(1, "Andy"), (2, "George"), (3, "Betty")] 245 | cursor.executemany("INSERT INTO t1 VALUES (?,?)", data) 246 | 247 | #commit pending transactions 248 | connection.commit() 249 | 250 | #close handles 251 | cursor.close() 252 | connection.close() 253 | 254 | 255 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ==================================================== 2 | MariaDB Connector/Python 3 | ==================================================== 4 | 5 | .. sectionauthor:: Georg Richter 6 | 7 | .. testsetup:: 8 | 9 | import mariadb 10 | conn_params= { 11 | "host" : "localhost", 12 | "database" : "test" 13 | } 14 | 15 | conn=mariadb.connect(**conn_params) 16 | cursor=conn.cursor() 17 | cursor.execute("CREATE USER IF NOT EXISTS example_user@localhost identified by 'GHbe_Su3B8'") 18 | cursor.execute("grant all on test.* to example_user@localhost") 19 | cursor.execute("DROP TABLE IF EXISTS book") 20 | cursor.close() 21 | conn.close() 22 | 23 | |MCP| enables python programs to access MariaDB and MySQL databases, using an API 24 | which is compliant with the Python |DBAPI|. It is written in C and Python and uses 25 | MariaDB Connector/C client library for client server communication. 26 | 27 | .. rubric:: Contents 28 | 29 | .. toctree:: 30 | :maxdepth: 3 31 | :caption: Contents: 32 | 33 | install 34 | usage 35 | pooling 36 | api 37 | license 38 | release 39 | bugs 40 | faq 41 | 42 | ------------------ 43 | Indices and tables 44 | ------------------ 45 | 46 | * :ref:`genindex` 47 | * :ref:`modindex` 48 | * :ref:`search` 49 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Installation 4 | ============ 5 | 6 | .. sectionauthor:: Georg Richter 7 | 8 | Prerequisites 9 | ^^^^^^^^^^^^^ 10 | 11 | The current |MCP| implementation supports 12 | 13 | * Python versions from 3.7 to 3.11 14 | * MariaDB server versions from version 10.3 or MySQL server versions from version 5.7. 15 | * MariaDB client library (MariaDB Connector/C) from version |MCC_minversion|. 16 | 17 | Binary installation 18 | ^^^^^^^^^^^^^^^^^^^ 19 | 20 | Microsoft Windows 21 | ----------------- 22 | 23 | To install |MCP| on Microsoft Windows, you first have to install a recent version of |MCC|. MSI installer for 24 | both 32-bit and 64-bit operating systems are available from |MCDP|. 25 | 26 | After installation of |MCC| download and install |MCP| with the following command: 27 | 28 | .. code-block:: console 29 | 30 | pip3 install mariadb 31 | 32 | On success, you should see a message at the end "Successfully installed mariadb-x.y.z", where x.y.z is 33 | the recent version of |MCP|. 34 | 35 | .. code-block:: console 36 | 37 | Collecting mariadb 38 | Downloading mariadb-1.1.5-cp310-cp310-win_amd64.whl (190 kB) 39 | ---------------------------------------- 190.9/190.9 kB 2.9 MB/s eta 0:00:00 40 | Installing collected packages: mariadb 41 | Successfully installed mariadb-1.1.5 42 | 43 | Installation from Source 44 | ^^^^^^^^^^^^^^^^^^^^^^^^ 45 | 46 | Build prerequisites 47 | ------------------- 48 | 49 | The following build prerequisites are required to install or build MariaDB Connector/Python from source code, github or from 50 | pypi.org. 51 | 52 | To install |MCP| from sources you will need: 53 | 54 | - C compiler 55 | - Python development files (Usually they are installed with package **python3-dev**). The minimum supported version of Python is 3.7. 56 | - |MCC| libraries and header files (Either from MariaDB server package or 57 | from |MCC| package). Minimum required version for |MCP| < 1.1.0 is 3.1.5, for later versions |MCC_minversion|. 58 | If your distribution doesn't provide a recent version of |MCC| you can either download binary packages from |MCDP| or build 59 | the package from source. 60 | - The mariadb_config program from MariaDB Connector/C, which should be in your PATH directory. 61 | - For Posix systems: TLS libraries, e.g. GnuTLS or OpenSSL (default) 62 | - Since MariaDB Connector/Python 1.1.5: Python's "packaging" module. 63 | 64 | On Posix systems make sure that the path environment variable contains the directory which 65 | contains the mariadb_config utility. 66 | 67 | Once everything is in place, run 68 | 69 | .. code-block:: console 70 | 71 | pip3 install mariadb 72 | 73 | or if you downloaded the source package 74 | 75 | .. code-block:: console 76 | 77 | cd source_package_dir 78 | python3 -m pip install . 79 | 80 | For troubleshooting please also check the chapter :ref:`installation_faq` from the FAQ page. 81 | 82 | Test suite 83 | ^^^^^^^^^^ 84 | 85 | If you have installed the sources, after successful build you can run the test suite 86 | from the source directory. 87 | 88 | .. code-block:: console 89 | 90 | cd testing 91 | python3 -m unittest discover -v 92 | 93 | You can configure the connection parameters by using the following environment variables 94 | 95 | * TEST_DB_USER (default root) 96 | * TEST_DB_PASSWORD 97 | * TEST_DB_DATABASE (default 'testp') 98 | * TEST_DB_HOST (default 'localhost') 99 | * TEST_DB_PORT (default 3306) 100 | -------------------------------------------------------------------------------- /docs/source/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | MariaDB Connector/Python 5 | ------------------------ 6 | MariaDB Connector/Python is licensed under the `GNU Lesser General Public License v2.1 `_ 7 | 8 | 9 | MariaDB Connector/Python documentation 10 | -------------------------------------- 11 | 12 | The documentation for MariaDB Connector/Python is covered by the `Creative Commons Attribution 3.0 license `_. 13 | 14 | You are **free**, to 15 | - Share, copy and redistribute the material in any medium or format 16 | - Adapt, remix, transform, and build upon the material for any purpose, even commercially. 17 | 18 | under the following **terms** 19 | - Attribution -- You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 20 | - No additional restrictions —- You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/source/module.rst: -------------------------------------------------------------------------------- 1 | .. _module: 2 | 3 | The MariaDB Connector/Python module 4 | =================================== 5 | 6 | .. sectionauthor:: Georg Richter 7 | 8 | .. automodule:: mariadb 9 | 10 | 11 | Constructors 12 | ------------ 13 | 14 | ---------- 15 | Connection 16 | ---------- 17 | 18 | .. autofunction:: mariadb.connect(connectionclass=mariadb.connections.Connection, **kwargs) 19 | 20 | .. note:: 21 | For a description of configuration file handling and settings please read the chapter `Configuration files `_ of the MariaDB Connector/C documentation. 22 | 23 | Example: 24 | 25 | .. testcode:: 26 | 27 | import mariadb 28 | 29 | connection= mariadb.connect(user="example_user", host="localhost", database="test", password="GHbe_Su3B8") 30 | 31 | print(connection.character_set) 32 | 33 | Output: 34 | 35 | .. testoutput:: 36 | 37 | utf8mb4 38 | 39 | --------------- 40 | Connection Pool 41 | --------------- 42 | 43 | .. autofunction:: mariadb.ConnectionPool(**kwargs) 44 | 45 | ----------------- 46 | Type constructors 47 | ----------------- 48 | 49 | .. autofunction:: mariadb.Binary() 50 | 51 | .. autofunction:: mariadb.Date(year, month, day) 52 | 53 | .. autofunction:: mariadb.DateFromTicks(ticks) 54 | 55 | .. autofunction:: mariadb.Time(hour, minute, second) 56 | 57 | .. autofunction:: mariadb.TimeFromTicks(ticks) 58 | 59 | .. autofunction:: mariadb.Timestamp(year, month, day, hour, minute, second) 60 | 61 | .. autofunction:: mariadb.TimestampFromTicks(ticks) 62 | 63 | Attributes 64 | ---------- 65 | 66 | .. attribute:: apilevel 67 | 68 | String constant stating the supported DB API level. The value for `mariadb` is 69 | ``2.0``. 70 | 71 | .. attribute:: threadsafety 72 | 73 | Integer constant stating the level of thread safety. For `mariadb` the value is 1, 74 | which means threads can share the module but not the connection. 75 | 76 | .. attribute:: paramstyle 77 | 78 | String constant stating the type of parameter marker. For `mariadb` the value is 79 | `qmark`. For compatibility reasons `mariadb` also supports the `format` and 80 | `pyformat` paramstyles with the limitation that they can't be mixed inside a SQL statement. 81 | 82 | .. attribute:: mariadbapi_version 83 | 84 | String constant stating the version of the used MariaDB Connector/C library. 85 | 86 | .. versionadded:: 1.1.0 87 | .. attribute:: client_version 88 | 89 | Returns the version of MariaDB Connector/C library in use as an integer. 90 | The number has the following format: 91 | MAJOR_VERSION * 10000 + MINOR_VERSION * 1000 + PATCH_VERSION 92 | 93 | .. versionadded:: 1.1.0 94 | .. attribute:: client_version_info 95 | 96 | Returns the version of MariaDB Connector/C library as a tuple in the 97 | following format: 98 | (MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) 99 | 100 | 101 | Exceptions 102 | ---------- 103 | 104 | Compliant to DB API 2.0 MariaDB Connector/C provides information about errors 105 | through the following exceptions: 106 | 107 | .. autoexception:: mariadb.DataError 108 | 109 | .. autoexception:: mariadb.DatabaseError 110 | 111 | .. autoexception:: mariadb.InterfaceError 112 | 113 | .. autoexception:: mariadb.Warning 114 | 115 | .. autoexception:: mariadb.PoolError 116 | 117 | .. autoexception:: mariadb.OperationalError 118 | 119 | .. autoexception:: mariadb.IntegrityError 120 | 121 | .. autoexception:: mariadb.InternalError 122 | 123 | .. autoexception:: mariadb.ProgrammingError 124 | 125 | .. autoexception:: mariadb.NotSupportedError 126 | 127 | ------------ 128 | Type objects 129 | ------------ 130 | 131 | .. 132 | _Note: Type objects are handled as constants, therefore we can't 133 | use autodata. 134 | 135 | MariaDB Connector/Python type objects are immutable sets for type settings 136 | and defined in DBAPI 2.0 (PEP-249). 137 | 138 | Example: 139 | 140 | .. testcode:: 141 | 142 | import mariadb 143 | from mariadb.constants import FIELD_TYPE 144 | 145 | print(FIELD_TYPE.GEOMETRY == mariadb.BINARY) 146 | print(FIELD_TYPE.DATE == mariadb.DATE) 147 | print(FIELD_TYPE.VARCHAR == mariadb.BINARY) 148 | 149 | Output: 150 | 151 | .. testoutput:: 152 | 153 | True 154 | True 155 | False 156 | 157 | .. data:: STRING 158 | 159 | This type object is used to describe columns in a database that are 160 | string-based (e.g. CHAR1). 161 | 162 | .. data:: BINARY 163 | 164 | This type object is used to describe (long) binary columns in a database 165 | (e.g. LONG, RAW, BLOBs). 166 | 167 | .. data:: NUMBER 168 | 169 | This type object is used to describe numeric columns in a database. 170 | 171 | .. data:: DATETIME 172 | 173 | This type object is used to describe date/time columns in a database. 174 | 175 | .. data:: ROWID 176 | 177 | This type object is not supported in MariaDB Connector/Python and represents 178 | an empty set. 179 | -------------------------------------------------------------------------------- /docs/source/pool.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | The ConnectionPool class 3 | ======================== 4 | 5 | .. sectionauthor:: Georg Richter 6 | 7 | .. autoclass:: mariadb.ConnectionPool 8 | 9 | ---------------------- 10 | ConnectionPool methods 11 | ---------------------- 12 | 13 | .. automethod:: mariadb.ConnectionPool.add_connection 14 | 15 | .. automethod:: mariadb.ConnectionPool.close 16 | 17 | .. automethod:: mariadb.ConnectionPool.get_connection 18 | 19 | .. automethod:: mariadb.ConnectionPool.set_config 20 | 21 | ------------------------- 22 | ConnectionPool attributes 23 | ------------------------- 24 | 25 | .. versionadded:: 1.1.0 26 | .. autoattribute:: mariadb.ConnectionPool.connection_count 27 | 28 | .. autoattribute:: mariadb.ConnectionPool.max_size 29 | 30 | .. autoattribute:: mariadb.ConnectionPool.pool_size 31 | 32 | .. autoattribute:: mariadb.ConnectionPool.pool_name 33 | 34 | .. versionadded:: 1.1.0 35 | .. autoattribute:: mariadb.ConnectionPool.pool_reset_connection 36 | 37 | -------------------------------------------------------------------------------- /docs/source/pooling.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Connection pooling 3 | ================== 4 | 5 | A connection pool is a cache of connections to a database server where connections can be reused for future requests. 6 | Since establishing a connection is resource-expensive and time-consuming, especially when used inside a middle tier 7 | environment which maintains multiple connections and requires connections to be immediately available on the fly. 8 | 9 | Especially for server-side web applications, a connection pool is the standard way to maintain a pool of database connections 10 | which are reused across requests. 11 | 12 | 13 | --------------------------------------- 14 | Configuring and using a connection pool 15 | --------------------------------------- 16 | 17 | The typical way for creating and using a connection pool is 18 | 19 | 1. Create (and configure) a connection pool 20 | 2. Obtain a connection from connection pool 21 | 3. Perform database operation(s) 22 | 4. Close the connection instance and return it to the connection pool. 23 | 24 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 25 | Creating a connection pool 26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 | 28 | When creating a connection pool, the following parameters have to be provided: 29 | 30 | 1. Connection pool specific parameters 31 | 32 | - pool_name: The name of the pool, if not specified |MCP| will raise an exception. 33 | - pool_size: The size of the pool, if not specified a default of 5 will be set. 34 | - pool_reset_session: If set to True, the connection will be resetted before returned to the pool 35 | 36 | .. versionadded:: 1.1.0 37 | 38 | - pool_invalidation_interval: specifies the validation interval in milliseconds after which the status of a connection requested from the pool is checked. The default values is 500 milliseconds, a value of 0 means that the status will always be checked. 39 | 40 | 41 | 2. Connection parameters 42 | 43 | - In addition to the connection pool specific parameters initialization method of ConnectionPool Class accepts the same parameters as the connect() method of mariadb module. 44 | 45 | *Example*: 46 | 47 | .. testcode:: 48 | 49 | import mariadb 50 | 51 | # connection parameters 52 | conn_params= { 53 | "user" : "example_user", 54 | "password" : "GHbe_Su3B8", 55 | "database" : "test" 56 | } 57 | 58 | # create new pool 59 | pool= mariadb.ConnectionPool(pool_name="myfirstpool", pool_size=5, **conn_params) 60 | print("Pool size of '%s': %s" % (pool.pool_name, pool.pool_size)) 61 | 62 | # get a connection from pool 63 | conn= pool.get_connection() 64 | 65 | # print the default database for connection 66 | print("Current database: %s" % conn.database) 67 | 68 | # close connection and return it to pool 69 | conn.close() 70 | 71 | *Output*: 72 | 73 | .. testoutput:: 74 | 75 | Pool size of 'myfirstpool': 5 76 | Current database: test 77 | 78 | -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | *********** 2 | Basic usage 3 | *********** 4 | 5 | .. sectionauthor:: Georg Richter 6 | 7 | Connecting 8 | ########## 9 | 10 | The basic usage of MariaDB Connector/Python is similar to other database drivers which 11 | implement |DBAPI|. 12 | 13 | Below is a simple example of a typical use of MariaDB Connector/Python 14 | 15 | .. testsetup:: 16 | 17 | import mariadb 18 | 19 | # connection parameters 20 | conn_params= { 21 | "user" : "example_user", 22 | "password" : "GHbe_Su3B8", 23 | "host" : "localhost", 24 | "database" : "test" 25 | } 26 | 27 | # Establish a connection 28 | connection= mariadb.connect(**conn_params) 29 | 30 | cursor= connection.cursor() 31 | cursor.execute("CREATE OR REPLACE TABLE `countries` (" 32 | "`id` int(10) unsigned NOT NULL AUTO_INCREMENT," 33 | "`name` varchar(50) NOT NULL," 34 | "`country_code` char(3) NOT NULL," 35 | "`capital` varchar(50) DEFAULT NULL," 36 | "PRIMARY KEY (`id`)," 37 | "KEY `name` (`name`)," 38 | "KEY `capital` (`capital`)" 39 | ") ENGINE=InnoDB DEFAULT CHARSET=latin1") 40 | 41 | cursor.close() 42 | connection.close() 43 | 44 | .. testcode:: 45 | 46 | import mariadb 47 | 48 | # connection parameters 49 | conn_params= { 50 | "user" : "example_user", 51 | "password" : "GHbe_Su3B8", 52 | "host" : "localhost", 53 | "database" : "test" 54 | } 55 | 56 | # Establish a connection 57 | connection= mariadb.connect(**conn_params) 58 | 59 | cursor= connection.cursor() 60 | 61 | # Populate countries table with some data 62 | cursor.execute("INSERT INTO countries(name, country_code, capital) VALUES (?,?,?)", 63 | ("Germany", "GER", "Berlin")) 64 | 65 | # retrieve data 66 | cursor.execute("SELECT name, country_code, capital FROM countries") 67 | 68 | # print content 69 | row= cursor.fetchone() 70 | print(*row, sep=' ') 71 | 72 | # free resources 73 | cursor.close() 74 | connection.close() 75 | 76 | *Output*: 77 | 78 | .. testoutput:: 79 | 80 | Germany GER Berlin 81 | 82 | 83 | Before MariaDB Connector/Python can be used, the MariaDB Connector/Python module must be 84 | imported. 85 | Once the mariadb module is loaded, a connection to a database server will be established 86 | using the method :func:`~mariadb.connect`. 87 | 88 | In order to be able to communicate with the database server in the form of SQL statements, 89 | a cursor object must be created first. 90 | 91 | The method name cursor may be a little misleading: unlike a cursor in MariaDB that can only 92 | read and return data, a cursor in Python can be used for all types of SQL statements. 93 | 94 | After creating the table mytest, everything is ready to insert some data: Column values 95 | that are to be inserted in the database are identified by place holders, the data is then passed in 96 | the form of a tuple as a second parameter. 97 | 98 | After creating and populating the table mytest the cursor will be used to retrieve the data. 99 | 100 | At the end we free resources and close cursor and connection. 101 | 102 | Passing parameters to SQL statements 103 | #################################### 104 | As shown in previous example, passing parameters to SQL statements happens by using placeholders in the statement. By default 105 | MariaDB Connector/Python uses a question mark as a placeholder, for compatibility reason also %s placeholders are supported. 106 | Passing parameters is supported in methods :func:`~execute` and :func:`~executemany` of the cursor class. 107 | 108 | Since |MCP| uses binary protocol, escaping strings or binary data like in other database drivers is not required. 109 | 110 | .. testcode:: 111 | 112 | import mariadb 113 | 114 | # connection parameters 115 | conn_params= { 116 | "user" : "example_user", 117 | "password" : "GHbe_Su3B8", 118 | "host" : "localhost", 119 | "database" : "test" 120 | } 121 | 122 | # Establish a connection 123 | connection= mariadb.connect(**conn_params) 124 | 125 | cursor= connection.cursor() 126 | 127 | sql= "INSERT INTO countries (name, country_code, capital) VALUES (?,?,?)" 128 | data= ("Germany", "GER", "Berlin") 129 | cursor.execute(sql, data) 130 | 131 | connection.commit() 132 | 133 | # delete last entry 134 | sql= "DELETE FROM countries WHERE country_code=?" 135 | data= ("GER",) 136 | cursor.execute(sql, data) 137 | 138 | connection.commit() 139 | 140 | cursor.close() 141 | connection.close() 142 | 143 | 144 | 145 | 146 | Often there is a requirement to update, delete or insert multiple records. This could be done be using :func:`~execute` in 147 | a loop, but much more effective is using the :func:`executemany` method, especially when using a MariaDB database server 10.2 and above, which supports a special "bulk" protocol. The executemany() works similar to execute(), but accepts data as a list of tuples: 148 | 149 | .. testcode:: python 150 | 151 | import mariadb 152 | 153 | # connection parameters 154 | conn_params= { 155 | "user" : "example_user", 156 | "password" : "GHbe_Su3B8", 157 | "host" : "localhost", 158 | "database" : "test" 159 | } 160 | 161 | # Establish a connection 162 | connection= mariadb.connect(**conn_params) 163 | 164 | cursor= connection.cursor() 165 | sql= "INSERT INTO countries (name, country_code, capital) VALUES (?,?,?)" 166 | 167 | data= [("Ireland", "IE", "Dublin"), 168 | ("Italy", "IT", "Rome"), 169 | ("Malaysia", "MY", "Kuala Lumpur"), 170 | ("France", "FR", "Paris"), 171 | ("Iceland", "IS", "Reykjavik"), 172 | ("Nepal", "NP", "Kathmandu")] 173 | 174 | # insert data 175 | cursor.executemany(sql, data) 176 | 177 | # Since autocommit is off by default, we need to commit last transaction 178 | connection.commit() 179 | 180 | # Instead of 3 letter country-code, we inserted 2 letter country code, so 181 | # let's fix this mistake by updating data 182 | sql= "UPDATE countries SET country_code=? WHERE name=?" 183 | data= [("Ireland", "IRL"), 184 | ("Italy", "ITA"), 185 | ("Malaysia", "MYS"), 186 | ("France", "FRA"), 187 | ("Iceland", "ISL"), 188 | ("Nepal", "NPL")] 189 | cursor.executemany(sql, data) 190 | 191 | # Now let's delete all non European countries 192 | sql= "DELETE FROM countries WHERE name=?" 193 | data= [("Malaysia",), ("Nepal",)] 194 | cursor.executemany(sql, data) 195 | 196 | # by default autocommit is off, so we need to commit 197 | # our transactions 198 | connection.commit() 199 | 200 | # free resources 201 | cursor.close() 202 | connection.close() 203 | 204 | When using executemany(), there are a few restrictions: 205 | - All tuples must have the same types as in first tuple. E.g. the parameter [(1),(1.0)] or [(1),(None)] are invalid. 206 | - Special values like None or column default value needs to be indicated by an indicator. 207 | 208 | Using indicators 209 | **************** 210 | 211 | In certain situations, for example when inserting default values or NULL, special indicators must be used. 212 | 213 | .. testcode:: 214 | 215 | import mariadb 216 | from mariadb.constants import * 217 | 218 | import mariadb 219 | 220 | # connection parameters 221 | conn_params= { 222 | "user" : "example_user", 223 | "password" : "GHbe_Su3B8", 224 | "host" : "localhost", 225 | "database" : "test" 226 | } 227 | 228 | # Establish a connection 229 | connection= mariadb.connect(**conn_params) 230 | 231 | cursor= connection.cursor() 232 | 233 | cursor.execute("DROP TABLE IF EXISTS cakes") 234 | cursor.execute("CREATE TABLE cakes(id int, cake varchar(100), price decimal(10,2) default 1.99)") 235 | 236 | sql= "INSERT INTO cakes (id, cake, price) VALUES (?,?,?)" 237 | data= [(1, "Cherry Cake", 2.10), (2, "Apple Cake", INDICATOR.DEFAULT)] 238 | cursor.executemany(sql, data) 239 | 240 | Beside the default indicator which inserts the default value of 1.99, the following indicators are supported: 241 | * INDICATOR.IGNORE: Ignores the value (only update commands) 242 | * INDICATOR.NULL: Value is NULL 243 | * INDICATOR.IGNORE_ROW: Don't update or insert row 244 | 245 | .. note:: 246 | * Mixing different parameter styles is not supported and will raise an exception 247 | * The Python string operator % must not be used. The :func:`~execute` method accepts a tuple or list as second parameter. 248 | * Placeholders between quotation marks are interpreted as a string. 249 | * Parameters for :func:`~execute` needs to be passed as a tuple. If only one parameter will be passed, tuple needs to contain a comma at the end. 250 | * Parameters for :func:`~executemany` need to be passed as a list of tuples. 251 | 252 | Supported Data types 253 | -------------------- 254 | 255 | Several standard python types are converted into SQL types and returned as Python objects when a statement is executed. 256 | 257 | .. list-table:: Supported Data Types 258 | :align: left 259 | :header-rows: 1 260 | 261 | * - Python type 262 | - SQL type 263 | * - None 264 | - NULL 265 | * - Bool 266 | - TINYINT 267 | * - Float, Double 268 | - DOUBLE 269 | * - Decimal 270 | - DECIMAL 271 | * - Long 272 | - TINYINT, SMALLINT, INT, BIGINT 273 | * - String 274 | - VARCHAR, VARSTRING, TEXT 275 | * - ByteArray, Bytes 276 | - TINYBLOB, MEDIUMBLOB, BLOB, LONGBLOB 277 | * - DateTime 278 | - DATETIME 279 | * - Date 280 | - DATE 281 | * - Time 282 | - TIME 283 | * - Timestamp 284 | - TIMESTAMP 285 | -------------------------------------------------------------------------------- /helper/create_errconst.py: -------------------------------------------------------------------------------- 1 | # 2 | # script for creating error constants 3 | # 4 | 5 | import requests 6 | 7 | ignore_definitions = ["ERR_ERROR_FIRST", "ER_ERROR_LAST", "CR_MIN_ERROR", 8 | "CR_MAX_ERROR", "CLIENT_ERRMAP", 9 | "CER_MIN_ERROR", "CER_MAX_ERROR", 10 | "CR_MYSQL_LAST_ERROR", "CR_MARIADB_LAST_ERROR"] 11 | 12 | files = ["https://raw.githubusercontent.com/mariadb-corporation/" 13 | "mariadb-connector-c/3.3/include/mysqld_error.h", 14 | "https://raw.githubusercontent.com/mariadb-corporation/" 15 | "mariadb-connector-c/3.3/include/errmsg.h"] 16 | 17 | error_definitions = [] 18 | 19 | for i in range(0, len(files)): 20 | errors = requests.get(files[i], allow_redirects=True) 21 | error_definitions += errors.content.decode("utf8").split("\n") 22 | 23 | print("# Autogenerated file. Please do not edit!\n\n") 24 | 25 | for i in range(0, len(error_definitions)): 26 | x = error_definitions[i].split() 27 | if (len(x) >= 3 and x[0] == "#define" and x[1] not in ignore_definitions 28 | and x[1][:9] != "ER_UNUSED"): 29 | try: 30 | if int(x[2]) > 0: 31 | print("%s = %s" % (x[1], x[2])) 32 | except Exception: 33 | pass 34 | -------------------------------------------------------------------------------- /include/docs/common.h: -------------------------------------------------------------------------------- 1 | #define __connect__doc__ \ 2 | "connect(*args, **kwargs)\n"\ 3 | "--\n"\ 4 | "\n"\ 5 | "Establishes a connection to a database server and returns a connection\n"\ 6 | "object.\n\n"\ 7 | "Connection parameters are provided as a set of keyword arguments:\n"\ 8 | "----------------------\n"\ 9 | "host: string\n"\ 10 | " The host name or IP address of the database server\n\n"\ 11 | "user: string\n"\ 12 | "username: string\n"\ 13 | " The username used to authenticate with the database server\n\n"\ 14 | "password: string\n"\ 15 | "passwd: string\n"\ 16 | " The password of the given user\n\n"\ 17 | "database: string\n"\ 18 | "db: string\n"\ 19 | " database (schema) name to use when connecting with the database\n"\ 20 | " server\n\n"\ 21 | "unix_socket: string\n"\ 22 | " The location of the unix socket file to use instead of using an IP port\n"\ 23 | " to connect. If socket authentication is enabled, this can also be used\n"\ 24 | " in place of a password.\n\n"\ 25 | "port: integer\n"\ 26 | " port number of the database server. If not specified the default\n"\ 27 | " value of 3306 will be used.\n\n"\ 28 | "connect_timeout: integer\n"\ 29 | " connect timeout in seconds\n\n"\ 30 | "read_timeout: integer\n"\ 31 | " read timeout in seconds\n\n"\ 32 | "write_timeout: integer\n"\ 33 | " write timeout in seconds\n\n"\ 34 | "local_infile: boolean\n"\ 35 | " Enables or disables the use of LOAD DATA LOCAL INFILE statements.\n\n"\ 36 | "compress: boolean\n"\ 37 | " Uses the compressed protocol for client server communication. If the\n"\ 38 | " server doesn't support compressed protocol, the default protocol will\n"\ 39 | " be used\n\n"\ 40 | "init_command: string\n"\ 41 | " Command(s) which will be executed when connecting and reconnecting to\n"\ 42 | " the database server\n\n"\ 43 | "default_file: string\n"\ 44 | " Read options from the specified option file. If the file is an empty\n"\ 45 | " string, default configuration file(s) will be used\n\n"\ 46 | "default_group: string\n"\ 47 | " Read options from the specified group\n\n"\ 48 | "ssl_key: string\n"\ 49 | " Defines a path to a private key file to use for TLS. This option\n"\ 50 | " requires that you use the absolute path, not a relative path. The\n"\ 51 | " specified key must be in PEM format\n\n"\ 52 | "ssl_cert: string\n"\ 53 | " Defines a path to the X509 certificate file to use for TLS.\n"\ 54 | " This option requires that you use the absolute path, not a relative\n"\ 55 | " path. The X609 certificate must be in PEM format.\n\n"\ 56 | "ssl_ca: string\n"\ 57 | " Defines a path to a PEM file that should contain one or more X509\n"\ 58 | " certificates for trusted Certificate Authorities (CAs) to use for TLS.\n"\ 59 | " This option requires that you use the absolute path, not a relative\n"\ 60 | " path.\n\n"\ 61 | "ssl_capath: string\n"\ 62 | " Defines a path to a directory that contains one or more PEM files that\n"\ 63 | " contains one X509 certificate for a trusted Certificate Authority (CA)\n\n"\ 64 | "ssl_cipher: string\n"\ 65 | " Defines a list of permitted cipher suites to use for TLS\n\n"\ 66 | "ssl_crlpath: string\n"\ 67 | " Defines a path to a PEM file that should contain one or more revoked\n"\ 68 | " X509 certificates to use for TLS. This option requires that you use\n"\ 69 | " the absolute path, not a relative path.\n\n"\ 70 | "ssl_verify_cert: boolean\n"\ 71 | " Enables server certificate verification.\n\n"\ 72 | "ssl: Boolean\n"\ 73 | " The connection must use TLS security or it will fail.\n\n"\ 74 | "autocommit: Boolean or None\n"\ 75 | " Specifies the autocommit settings: None will use the server default,"\ 76 | " True will enable autocommit, False will disable it (default).\n\n" 77 | -------------------------------------------------------------------------------- /include/docs/connection.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************ 2 | Copyright (C) 2019 Georg Richter and MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | *************************************************************************************/ 19 | PyDoc_STRVAR( 20 | connection_connect__doc__, 21 | __connect__doc__ 22 | ); 23 | 24 | PyDoc_STRVAR( 25 | connection__doc__, 26 | "The Connection class is used to open and manage a connection to a\n" 27 | "MariaDB or compatible database server" 28 | ); 29 | 30 | PyDoc_STRVAR( 31 | connection_dump_debug_info__doc__, 32 | "dump_debug_info()\n" 33 | "--\n" 34 | "\n" 35 | "This function is designed to be executed by an user with the SUPER privilege\n" 36 | "and is used to dump server status information into the log for the MariaDB\n" 37 | "Server relating to the connection." 38 | ); 39 | 40 | PyDoc_STRVAR( 41 | connection_close__doc__, 42 | "close()\n" 43 | "--\n" 44 | "\n" 45 | "Close the connection now (rather than whenever .__del__() is called).\n\n" 46 | "The connection will be unusable from this point forward; an Error\n" 47 | "(or subclass) exception will be raised if any operation is attempted\n" 48 | "with the connection. The same applies to all cursor objects trying to\n" 49 | "use the connection.\n\n" 50 | "Note that closing a connection without committing the changes first\n" 51 | "will cause an implicit rollback to be performed." 52 | ); 53 | 54 | PyDoc_STRVAR( 55 | connection_change_user__doc__, 56 | "change_user(user: str, password: str, database: str)\n" 57 | "--\n" 58 | "\n" 59 | "Changes the user and default database of the current connection\n\n" 60 | "Parameters:\n" 61 | " - user: user name\n" 62 | " - password: password\n" 63 | " - database: name of default database\n\n" 64 | "In order to successfully change users a valid username and password\n" 65 | "parameters must be provided and that user must have sufficient\n" 66 | "permissions to access the desired database. If for any reason\n" 67 | "authorization fails an exception will be raised and the current user\n" 68 | "authentication will remain." 69 | ); 70 | 71 | PyDoc_STRVAR( 72 | connection_reconnect__doc__, 73 | "reconnect()\n" 74 | "--\n" 75 | "\n" 76 | "tries to reconnect to a server in case the connection died due to timeout\n" 77 | "or other errors. It uses the same credentials which were specified in\n" 78 | "connect() method." 79 | ); 80 | 81 | PyDoc_STRVAR( 82 | connection_reset__doc__, 83 | "reset()\n" 84 | "--\n" 85 | "\n" 86 | "Resets the current connection and clears session state and pending\n" 87 | "results. Open cursors will become invalid and cannot be used anymore." 88 | ); 89 | 90 | PyDoc_STRVAR( 91 | connection_escape_string__doc__, 92 | "escape_string(statement)\n" 93 | "--\n" 94 | "\n" 95 | "Parameters:\n" 96 | "statement: string\n\n" 97 | "This function is used to create a legal SQL string that you can use in\n" 98 | "an SQL statement. The given string is encoded to an escaped SQL string." 99 | ); 100 | 101 | /* ok */ 102 | PyDoc_STRVAR( 103 | connection_ping__doc__, 104 | "ping()\n" 105 | "--\n" 106 | "\n" 107 | "Checks if the connection to the database server is still available.\n\n" 108 | "If auto reconnect was set to true, an attempt will be made to reconnect\n" 109 | "to the database server in case the connection\n" 110 | "was lost\n\n" 111 | "If the connection is not available an InterfaceError will be raised." 112 | ); 113 | 114 | PyDoc_STRVAR( 115 | connection_auto_reconnect__doc__, 116 | "(read/write)\n\n" 117 | "Enable or disable automatic reconnection to the server if the connection\n" 118 | "is found to have been lost.\n\n" 119 | "When enabled, client tries to reconnect to a database server in case\n" 120 | "the connection to a database server died due to timeout or other errors." 121 | ); 122 | 123 | PyDoc_STRVAR( 124 | connection_warnings__doc__, 125 | "Returns the number of warnings from the last executed statement, or zero\n" 126 | "if there are no warnings." 127 | ); 128 | -------------------------------------------------------------------------------- /include/docs/cursor.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************ 2 | Copyright (C) 2019 Georg Richter and MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | *************************************************************************************/ 19 | 20 | PyDoc_STRVAR( 21 | cursor_description__doc__, 22 | "This read-only attribute is a sequence of 11-item sequences\n" 23 | "Each of these sequences contains information describing one result column:\n\n" 24 | "- name\n" 25 | "- type_code\n" 26 | "- display_size\n" 27 | "- internal_size\n" 28 | "- precision\n" 29 | "- scale\n" 30 | "- null_ok\n" 31 | "- field_flags\n" 32 | "- table_name\n" 33 | "- original_column_name\n" 34 | "- original_table_name\n\n" 35 | "This attribute will be None for operations that do not return rows or if the cursor has\n" 36 | "not had an operation invoked via the .execute*() method yet.\n\n" 37 | ); 38 | 39 | PyDoc_STRVAR( 40 | cursor_metadata__doc__, 41 | "Similar to description property, this property returns a dictionary with complete metadata.\n\n" 42 | "The dictionary contains the following keys:\n\n" 43 | "- catalog: catalog (always 'def')\n" 44 | "- schema: current schema\n" 45 | "- field: alias column name or if no alias was specified column name\n" 46 | "- org_field: original column name\n" 47 | "- table: alias table name or if no alias was specified table name\n" 48 | "- org_table: original table name\n" 49 | "- type: column type\n" 50 | "- charset: character set (utf8mb4 or binary)\n" 51 | "- length: The length of the column\n" 52 | "- max length: The maximum length of the column\n" 53 | "- decimals: The numer of decimals\n" 54 | "- flags: Flags (flags are defined in constants.FIELD_FLAG)\n" 55 | "- ext_type: Extended data type (types are defined in constants.EXT_FIELD_TYPE)\n" 56 | ); 57 | 58 | PyDoc_STRVAR( 59 | cursor_warnings__doc__, 60 | "Returns the number of warnings from the last executed statement, or zero\n" 61 | "if there are no warnings.\n\n" 62 | ); 63 | 64 | PyDoc_STRVAR( 65 | cursor_closed__doc__, 66 | "Indicates if the cursor is closed and can't be reused" 67 | ); 68 | 69 | PyDoc_STRVAR( 70 | cursor_buffered__doc__, 71 | "When True all result sets are immediately transferred and the connection\n" 72 | "between client and server is no longer blocked. Since version 1.1.0 default\n" 73 | "is True, for prior versions default was False." 74 | ); 75 | 76 | PyDoc_STRVAR( 77 | cursor_close__doc__, 78 | "close()\n" 79 | "--\n" 80 | "\n" 81 | "Closes the cursor. If the cursor has pending or unread results, .close()\n" 82 | "will cancel them so that further operations using the same connection\n" 83 | "can be executed.\n\n" 84 | "The cursor will be unusable from this point forward; an Error (or subclass)\n" 85 | "exception will be raised if any operation is attempted with the cursor." 86 | ); 87 | 88 | PyDoc_STRVAR( 89 | cursor_fetchone__doc__, 90 | "fetchone()\n" 91 | "--\n" 92 | "\n" 93 | "Fetches next row of a pending result set and returns a tuple.\n" 94 | ); 95 | 96 | PyDoc_STRVAR( 97 | cursor_field_count__doc__, 98 | "field_count()\n" 99 | "--\n" 100 | "\n" 101 | "Returns the number of fields (columns) of a result set." 102 | ); 103 | 104 | PyDoc_STRVAR( 105 | cursor_nextset__doc__, 106 | "nextset()\n" 107 | "--\n" 108 | "\n" 109 | "Will make the cursor skip to the next available result set,\n" 110 | "discarding any remaining rows from the current set." 111 | ); 112 | 113 | PyDoc_STRVAR( 114 | cursor_next__doc__, 115 | "next()\n" 116 | "--\n" 117 | "\n" 118 | "Return the next row from the currently executed SQL statement\n" 119 | "using the same semantics as .fetchone()." 120 | ); 121 | 122 | PyDoc_STRVAR( 123 | cursor_statement__doc__, 124 | "(read only)\n\n" 125 | "The last executed statement" 126 | ); 127 | 128 | PyDoc_STRVAR( 129 | cursor_rownumber__doc__, 130 | "(read only)\n\n" 131 | "Current row number in result set" 132 | ); 133 | 134 | PyDoc_STRVAR( 135 | cursor_arraysize__doc__, 136 | "(read/write)\n\n" 137 | "the number of rows to fetch" 138 | ); 139 | 140 | PyDoc_STRVAR( 141 | cursor_paramcount__doc__, 142 | "(read)\n\n" 143 | "Returns the number of parameter markers present in the executed statement." 144 | ); 145 | -------------------------------------------------------------------------------- /include/docs/exception.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | Copyright (C) 2020 Georg Richter and MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | ******************************************************************************/ 19 | 20 | PyDoc_STRVAR( 21 | exception_interface__doc__, 22 | "Exception raised for errors that are related to the database interface "\ 23 | "rather than the database itself" 24 | ); 25 | 26 | PyDoc_STRVAR( 27 | exception_warning__doc__, 28 | "Exception raised for important warnings like data truncations "\ 29 | "while inserting, etc" 30 | ); 31 | 32 | PyDoc_STRVAR( 33 | exception_database__doc__, 34 | "Exception raised for errors that are related to the database" 35 | ); 36 | 37 | PyDoc_STRVAR( 38 | exception_data__doc__, 39 | "Exception raised for errors that are due to problems with the "\ 40 | "processed data like division by zero, numeric value out of range, etc." 41 | ); 42 | 43 | PyDoc_STRVAR( 44 | exception_pool__doc__, 45 | "Exception raised for errors related to ConnectionPool class." 46 | ); 47 | 48 | PyDoc_STRVAR( 49 | exception_operational__doc__, 50 | "Exception raised for errors that are related to the database's "\ 51 | "operation and not necessarily under the control of the programmer." 52 | ); 53 | 54 | PyDoc_STRVAR( 55 | exception_integrity__doc__, 56 | "Exception raised when the relational integrity of the database "\ 57 | "is affected, e.g. a foreign key check fails" 58 | ); 59 | 60 | PyDoc_STRVAR( 61 | exception_internal__doc__, 62 | "Exception raised when the database encounters an internal error, "\ 63 | "e.g. the cursor is not valid anymore"; 64 | ); 65 | 66 | PyDoc_STRVAR( 67 | exception_programming__doc__, 68 | "Exception raised for programming errors, e.g. table not found or "\ 69 | "already exists, syntax error in the SQL statement" 70 | ); 71 | 72 | PyDoc_STRVAR( 73 | exception_notsupported__doc__, 74 | "Exception raised in case a method or database API was used which is "\ 75 | "not supported by the database" 76 | ); 77 | -------------------------------------------------------------------------------- /include/docs/module.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************ 2 | Copyright (C) 2019 Georg Richter and MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | *************************************************************************************/ 19 | PyDoc_STRVAR( 20 | module_connect__doc__, 21 | __connect__doc__ 22 | ); 23 | -------------------------------------------------------------------------------- /mariadb/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MariaDB Connector/Python module enables python programs to access MariaDB and 3 | MySQL databases, using an API which is compliant with the Python DB API 2.0 4 | (PEP-249). 5 | ''' 6 | import mariadb 7 | from ._mariadb import ( 8 | DataError, 9 | DatabaseError, 10 | Error, 11 | IntegrityError, 12 | InterfaceError, 13 | InternalError, 14 | NotSupportedError, 15 | OperationalError, 16 | PoolError, 17 | ProgrammingError, 18 | Warning, 19 | mariadbapi_version, 20 | _have_asan, 21 | ) 22 | 23 | from .field import fieldinfo 24 | from mariadb.dbapi20 import * # noqa: F401,F403 25 | from mariadb.connectionpool import * # noqa: F401,F403 26 | from mariadb.cursors import Cursor 27 | from mariadb.release_info import __version__ as __version__ 28 | from mariadb.release_info import __version_info__ as __version_info__ 29 | from mariadb.release_info import __author__ as __author__ 30 | from mariadb.connections import Connection 31 | # disable for now, until tests are in place 32 | # from mariadb.pooling import * 33 | 34 | _POOLS = _CONNECTION_POOLS = {} 35 | 36 | __all__ = ["DataError", "DatabaseError", "Error", "IntegrityError", 37 | "InterfaceError", "InternalError", "NotSupportedError", 38 | "OperationalError", "PoolError", "ProgrammingError", 39 | "Warning", "Connection", "__version__", "__version_info__", 40 | "__author__", "Cursor", "fieldinfo", "_have_asan"] 41 | 42 | 43 | def connect(*args, connectionclass=mariadb.connections.Connection, **kwargs): 44 | """ 45 | Creates a MariaDB Connection object. 46 | 47 | By default the standard connectionclass mariadb.connections.Connection 48 | will be created. 49 | 50 | Parameter connectionclass specifies a subclass of 51 | mariadb.Connection object. If not specified default will be used. 52 | This optional parameter was added in version 1.1.0. 53 | 54 | Connection parameters are provided as a set of keyword arguments: 55 | - host: 56 | The host name or IP address of the database server. 57 | If MariaDB Connector/Python was built with MariaDB Connector/C 3.3 58 | it is also possible to provide a comma separated list of hosts for 59 | simple fail over in case of one or more hosts are not available. 60 | - user, username: 61 | The username used to authenticate with the database server 62 | - password, passwd: 63 | The password of the given user 64 | - database, db: 65 | database (schema) name to use when connecting with the database 66 | server 67 | - unix_socket: 68 | The location of the unix socket file to use instead of using an IP 69 | port to connect. If socket authentication is enabled, this can also 70 | be used in place of a password. 71 | - port: 72 | port number of the database server. If not specified the default 73 | value of 3306 will be used. 74 | - connect_timeout: 75 | connect timeout in seconds 76 | - read_timeout: 77 | read timeout in seconds 78 | - write_timeout: 79 | write timeout in seconds 80 | - local_infile: 81 | Enables or disables the use of LOAD DATA LOCAL INFILE statements. 82 | - compress= False: 83 | Uses the compressed protocol for client server communication. If 84 | the server doesn't support compressed protocol, the default 85 | protocol will be used. 86 | - init_command: 87 | Command(s) which will be executed when connecting and reconnecting 88 | to the database server 89 | - default_file: 90 | Read options from the specified option file. If the file is an 91 | empty string, default configuration file(s) will be used 92 | - default_group: 93 | Read options from the specified group 94 | - plugin_dir: 95 | Directory which contains MariaDB client plugins. 96 | - reconnect: 97 | Enables or disables automatic reconnect. Available since 98 | version 1.1.4 99 | - ssl_key: 100 | Defines a path to a private key file to use for TLS. This option 101 | requires that you use the absolute path, not a relative path. The 102 | specified key must be in PEM format 103 | - ssl_cert: 104 | Defines a path to the X509 certificate file to use for TLS. 105 | This option requires that you use the absolute path, not a relative 106 | path. The X609 certificate must be in PEM format. 107 | - ssl_ca: 108 | Defines a path to a PEM file that should contain one or more X509 109 | certificates for trusted Certificate Authorities (CAs) to use for 110 | TLS. This option requires that you use the absolute path, not a 111 | relative path. 112 | - ssl_capath: 113 | Defines a path to a directory that contains one or more PEM files 114 | that contains one X509 certificate for a trusted Certificate 115 | Authority (CA) 116 | - ssl_cipher: 117 | Defines a list of permitted cipher suites to use for TLS 118 | - ssl_crlpath: 119 | Defines a path to a PEM file that should contain one or more 120 | revoked X509 certificates to use for TLS. This option requires 121 | that you use the absolute path, not a relative path. 122 | - ssl_verify_cert: 123 | Enables server certificate verification. 124 | - ssl: 125 | The connection must use TLS security or it will fail. 126 | - tls_version: 127 | A comma-separated list (without whitespaces) of TLS versions. 128 | Valid versions are TLSv1.0, TLSv1.1,TLSv1.2 and TLSv1.3. 129 | Added in version 1.1.7. 130 | - autocommit=False: 131 | Specifies the autocommit settings. 132 | True will enable autocommit, False will disable it (default). 133 | - converter: 134 | Specifies a conversion dictionary, where keys are FIELD_TYPE 135 | values and values are conversion functions 136 | 137 | """ 138 | if kwargs: 139 | if "pool_name" in kwargs: 140 | if not kwargs["pool_name"] in mariadb._CONNECTION_POOLS: 141 | pool = mariadb.ConnectionPool(**kwargs) 142 | else: 143 | pool = mariadb._CONNECTION_POOLS[kwargs["pool_name"]] 144 | c = pool.get_connection() 145 | return c 146 | 147 | connection = connectionclass(*args, **kwargs) 148 | if not isinstance(connection, mariadb.connections.Connection): 149 | raise mariadb.ProgrammingError("%s is not an instance of " 150 | "mariadb.Connection" % connection) 151 | return connection 152 | 153 | 154 | client_version_info = tuple(int(x, 10) for x in mariadbapi_version.split('.')) 155 | client_version = client_version_info[0] * 10000 +\ 156 | client_version_info[1] * 1000 + client_version_info[2] 157 | -------------------------------------------------------------------------------- /mariadb/connectionpool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020-2021 Georg Richter and MariaDB Corporation AB 3 | 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Library General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2 of the License, or (at your option) any later version. 8 | 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Library General Public License for more details. 13 | 14 | # You should have received a copy of the GNU Library General Public 15 | # License along with this library; if not see 16 | # or write to the Free Software Foundation, Inc., 17 | # 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | # 19 | 20 | import mariadb 21 | import _thread 22 | import time 23 | 24 | from mariadb.constants import STATUS 25 | 26 | MAX_POOL_SIZE = 64 27 | 28 | 29 | class ConnectionPool(object): 30 | """ 31 | Class defining a pool of database connections 32 | 33 | MariaDB Connector/Python supports simple connection pooling. 34 | A connection pool holds a number of open connections and handles 35 | thread safety when providing connections to threads. 36 | 37 | The size of a connection pool is configurable at creation time, 38 | but cannot be changed afterwards. The maximum size of a connection 39 | pool is limited to 64 connections. 40 | 41 | Keyword Arguments: 42 | 43 | * pool_name (str) -- Name of connection pool 44 | 45 | * pool_size (int)=5 -- Size of pool. If not specified default value 46 | of 5 will be used. Maximum allowed number is 64. 47 | 48 | * pool_reset_connection (bool)=True -- Will reset the connection before 49 | returning it to the pool. Default value is True. 50 | 51 | * pool_validation_interval (int)=500 -- Specifies the validation 52 | interval in milliseconds after which the status of a connection 53 | requested from the pool is checked. 54 | The default value is 500 milliseconds, a value of 0 means that 55 | the status will always be checked. 56 | (Added in version 1.1.6) 57 | 58 | * **kwargs: Optional additional connection arguments, as described in 59 | mariadb.connect() method. 60 | """ 61 | 62 | def __init__(self, *args, **kwargs): 63 | """ 64 | Creates a connection pool class 65 | 66 | :param str pool_name: 67 | Name of connection pool 68 | 69 | :param int pool_size: 70 | Size of pool. If not specified default value of 5 will be used. 71 | Maximum allowed number is 64. 72 | 73 | :param bool pool_reset_connection: 74 | Will reset the connection before returning it to the pool. 75 | Default value is True. 76 | 77 | :param **kwargs kwargs: 78 | Optional additional connection arguments, as described in 79 | mariadb.connect() method. 80 | """ 81 | self._connections_free = [] 82 | self._connections_used = [] 83 | self._pool_args = {} 84 | self._conn_args = {} 85 | self._lock_pool = _thread.RLock() 86 | self.__closed = 0 87 | 88 | key_words = ["pool_name", "pool_size", "pool_reset_connection", 89 | "pool_validation_interval"] 90 | 91 | # check if pool_name was provided 92 | if kwargs and "pool_name" in kwargs: 93 | 94 | # check if pool_name already exists 95 | if kwargs["pool_name"] in mariadb._CONNECTION_POOLS: 96 | raise mariadb.ProgrammingError("Pool '%s' already exists" 97 | % kwargs["pool_name"]) 98 | else: 99 | raise mariadb.ProgrammingError("No pool name specified") 100 | 101 | # save pool keyword arguments 102 | self._pool_args["name"] = kwargs.get("pool_name") 103 | self._pool_args["size"] = int(kwargs.get("pool_size", 5)) 104 | self._pool_args["reset_connection"] = \ 105 | bool(kwargs.get("pool_reset_connection", True)) 106 | self._pool_args["validation_interval"] = \ 107 | int(kwargs.get("pool_validation_interval", 500)) 108 | 109 | # validate pool size (must be in range between 1 and MAX_POOL_SIZE) 110 | if not (0 < self._pool_args["size"] <= MAX_POOL_SIZE): 111 | raise mariadb.ProgrammingError("Pool size must be in range of " 112 | "1 and %s" % MAX_POOL_SIZE) 113 | 114 | # store pool and connection arguments 115 | self._conn_args = kwargs.copy() 116 | for key in key_words: 117 | if key in self._conn_args: 118 | del self._conn_args[key] 119 | 120 | if len(self._conn_args) > 0: 121 | with self._lock_pool: 122 | # fill connection pool 123 | for i in range(0, self._pool_args["size"]): 124 | try: 125 | connection = mariadb.Connection(**self._conn_args) 126 | except mariadb.Error: 127 | # if an error occurred, close all connections 128 | # and raise exception 129 | for j in range(0, len(self._connections_free)): 130 | try: 131 | self._connections_free[j].close() 132 | except mariadb.Error: 133 | # connect failed, so we are not 134 | # interested in errors 135 | # from close() method 136 | pass 137 | del self._connections_free[j] 138 | raise 139 | self.add_connection(connection) 140 | 141 | # store connection pool in _CONNECTION_POOLS 142 | mariadb._CONNECTION_POOLS[self._pool_args["name"]] = self 143 | 144 | def _replace_connection(self, connection): 145 | """ 146 | Removes the given connection and adds a new connection. 147 | """ 148 | 149 | if connection: 150 | if connection in self._connections_free: 151 | x = self._connections_free.index(connection) 152 | del self._connections_free[x] 153 | elif connection in self._connections_used: 154 | x = self._connections_used.index(connection) 155 | del self._connections_used[x] 156 | 157 | connection._Connection__pool = None 158 | connection.close() 159 | return self.add_connection() 160 | 161 | def __repr__(self): 162 | if (self.__closed): 163 | return "" % (hex(id(self)),) 165 | else: 166 | return "" % (self.pool_name, hex(id(self))) 168 | 169 | def add_connection(self, connection=None): 170 | """ 171 | Adds a connection object to the connection pool. 172 | 173 | In case that the pool doesn’t have a free slot or is not configured 174 | a PoolError exception will be raised. 175 | """ 176 | 177 | if not self._conn_args: 178 | raise mariadb.PoolError("Couldn't get configuration for pool %s" % 179 | self._pool_args["name"]) 180 | 181 | if (connection is not None and 182 | not isinstance(connection, mariadb.connections.Connection)): 183 | raise mariadb.ProgrammingError("Passed parameter is not a " 184 | "connection object") 185 | 186 | if connection is None and len(self._conn_args) == 0: 187 | raise mariadb.PoolError("Can't get configuration for pool %s" % 188 | self._pool_args["name"]) 189 | 190 | total = len(self._connections_free + self._connections_used) 191 | if total >= self._pool_args["size"]: 192 | raise mariadb.PoolError("Can't add connection to pool %s: " 193 | "No free slot available (%s)." % 194 | (self._pool_args["name"], 195 | total)) 196 | 197 | with self._lock_pool: 198 | if connection is None: 199 | connection = mariadb.Connection(**self._conn_args) 200 | 201 | connection._Connection__pool = self 202 | connection.__last_used = time.perf_counter_ns() 203 | self._connections_free.append(connection) 204 | return connection 205 | 206 | def get_connection(self): 207 | """ 208 | Returns a connection from the connection pool or raises a PoolError 209 | exception if a connection is not available. 210 | """ 211 | 212 | conn = None 213 | 214 | with self._lock_pool: 215 | for i in range(0, len(self._connections_free)): 216 | conn = self._connections_free[i] 217 | dt = (time.perf_counter_ns() - conn.__last_used) / 1000000 218 | if dt > self._pool_args["validation_interval"]: 219 | try: 220 | conn.ping() 221 | except mariadb.Error: 222 | conn = self._replace_connection(conn) 223 | if not conn: 224 | continue 225 | 226 | conn._used += 1 227 | self._connections_used.append(conn) 228 | idx = self._connections_free.index(conn) 229 | del self._connections_free[idx] 230 | return conn 231 | 232 | raise mariadb.PoolError("No connection available") 233 | 234 | def _close_connection(self, connection): 235 | """ 236 | Returns connection to the pool. Internally used 237 | by connection object. 238 | """ 239 | with self._lock_pool: 240 | 241 | try: 242 | if self._pool_args["reset_connection"]: 243 | connection.reset() 244 | elif connection.server_status & STATUS.IN_TRANS: 245 | connection.rollback() 246 | except mariadb.Error: 247 | self._replace_connection(connection) 248 | 249 | if connection: 250 | if connection in self._connections_used: 251 | x = self._connections_used.index(connection) 252 | del self._connections_used[x] 253 | connection.__last_used = time.perf_counter_ns() 254 | self._connections_free.append(connection) 255 | 256 | def set_config(self, **kwargs): 257 | """ 258 | Sets the connection configuration for the connection pool. 259 | For valid connection arguments check the mariadb.connect() method. 260 | 261 | Note: This method doesn't create connections in the pool. 262 | To fill the pool one has to use add_connection() ḿethod. 263 | """ 264 | 265 | self._conn_args = kwargs 266 | 267 | def close(self): 268 | """Closes connection pool and all connections.""" 269 | try: 270 | for c in (self._connections_free + self._connections_used): 271 | c._Connection__pool = None 272 | c.close() 273 | finally: 274 | self._connections_free = None 275 | self._connections_used = None 276 | del mariadb._CONNECTION_POOLS[self._pool_args["name"]] 277 | 278 | @property 279 | def pool_name(self): 280 | """Returns the name of the connection pool.""" 281 | 282 | return self._pool_args["name"] 283 | 284 | @property 285 | def pool_size(self): 286 | """Returns the size of the connection pool.""" 287 | 288 | return self._pool_args["size"] 289 | 290 | @property 291 | def max_size(self): 292 | "Returns the maximum size for connection pools.""" 293 | 294 | return MAX_POOL_SIZE 295 | 296 | @property 297 | def connection_count(self): 298 | "Returns the number of connections in connection pool.""" 299 | 300 | try: 301 | return len(self._connections_free + self._connections_used) 302 | except Exception: 303 | return 0 304 | 305 | @property 306 | def pool_reset_connection(self): 307 | """ 308 | If set to true, the connection will be reset on both client and server 309 | side after .close() method was called 310 | """ 311 | return self._pool_args["reset_connection"] 312 | 313 | @pool_reset_connection.setter 314 | def pool_reset_connection(self, reset): 315 | self._pool_args["reset_connection"] = reset 316 | -------------------------------------------------------------------------------- /mariadb/constants/CAPABILITY.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MariaDB capability flags. 3 | 4 | These flags are used to check the capabilities both of a MariaDB server 5 | or the client applicaion. 6 | 7 | Capability flags are defined in module *mariadb.constants.CAPABILIY* 8 | 9 | ''' 10 | 11 | MYSQL = 1 # MariaDB 12 | LONG_PASSWORD = 1 # MySQL 13 | FOUND_ROWS = 2 14 | LONG_FLAG = 4 15 | CONNECT_WITH_DB = 8 16 | NO_SCHEMA = 16 17 | COMPRESS = 32 18 | LOCAL_FILES = 128 19 | IGNORE_SPACE = 256 20 | INTERACTIVE = 1024 21 | SSL = 2048 22 | TRANSACTIONS = 8192 23 | SECURE_CONNECTION = 32768 24 | MULTI_STATEMENTS = 1 << 16 25 | MULTI_RESULTS = 1 << 17 26 | PS_MULTI_RESULTS = 1 << 18 27 | PLUGIN_AUTH = 1 << 19 28 | CONNECT_ATTRS = 1 << 20 29 | CAN_HANDLE_EXPIRED_PASSWORDS = 1 < 22 30 | SESSION_TRACKING = 1 << 23 31 | SSL_VERIFY_SERVER_CERT = 1 << 30 32 | REMEMBER_OPTIONS = 1 << 31 33 | 34 | # MariaDB specific capabilities 35 | PROGRESS = 1 << 32 36 | BULK_OPERATIONS = 1 << 34 37 | EXTENDED_METADATA = 1 << 35 38 | CACHE_METDATA = 1 << 36 39 | -------------------------------------------------------------------------------- /mariadb/constants/CLIENT.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MariaDB capability flags. 3 | 4 | These flags are used to check the capabilities both of a MariaDB server 5 | or the client applicaion. 6 | 7 | Capability flags are defined in module *mariadb.constants.CLIENT* 8 | 9 | ''' 10 | 11 | MYSQL = 1 # MariaDB 12 | LONG_PASSWORD = 1 # MySQL 13 | FOUND_ROWS = 2 14 | LONG_FLAG = 4 15 | CONNECT_WITH_DB = 8 16 | NO_SCHEMA = 16 17 | COMPRESS = 32 18 | LOCAL_FILES = 128 19 | IGNORE_SPACE = 256 20 | INTERACTIVE = 1024 21 | SSL = 2048 22 | TRANSACTIONS = 8192 23 | SECURE_CONNECTION = 32768 24 | MULTI_STATEMENTS = 1 << 16 25 | MULTI_RESULTS = 1 << 17 26 | PS_MULTI_RESULTS = 1 << 18 27 | PLUGIN_AUTH = 1 << 19 28 | CONNECT_ATTRS = 1 << 20 29 | CAN_HANDLE_EXPIRED_PASSWORDS = 1 < 22 30 | SESSION_TRACKING = 1 << 23 31 | SSL_VERIFY_SERVER_CERT = 1 << 30 32 | REMEMBER_OPTIONS = 1 << 31 33 | 34 | # MariaDB specific capabilities 35 | PROGRESS = 1 << 32 36 | BULK_OPERATIONS = 1 << 34 37 | EXTENDED_METADATA = 1 << 35 38 | CACHE_METDATA = 1 << 36 39 | -------------------------------------------------------------------------------- /mariadb/constants/CURSOR.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cursor constants are used for server side cursors. 3 | Currently only read only cursor is supported. 4 | 5 | Cursor constants are defined in module *mariadb.constants.CURSOR*. 6 | """ 7 | 8 | NONE = 0 9 | READ_ONLY = 1 10 | -------------------------------------------------------------------------------- /mariadb/constants/EXT_FIELD_TYPE.py: -------------------------------------------------------------------------------- 1 | """ 2 | MariaDB EXT_FIELD_TYPE Constants 3 | 4 | These constants represent the extended field types supported by MariaDB. 5 | 6 | Extended field types are defined in module *mariadb.constants.EXT_FIELD_TYPE* 7 | """ 8 | 9 | NONE =0 10 | JSON = 1 11 | UUID = 2 12 | INET4 = 3 13 | INET6 = 4 14 | POINT = 5 15 | MULTIPOINT = 6 16 | LINESTRING = 7 17 | MULTILINESTRING = 8 18 | POLYGON = 9 19 | MULTIPOLYGON = 10 20 | GEOMETRYCOLLECTION = 11 21 | -------------------------------------------------------------------------------- /mariadb/constants/FIELD_FLAG.py: -------------------------------------------------------------------------------- 1 | """MariaDB FIELD_FLAG Constants 2 | 3 | These constants represent the various field flags. As an addition 4 | to the DBAPI 2.0 standard (PEP-249) these flags are returned as 5 | eighth element of the cursor description attribute. 6 | 7 | Field flags are defined in module *mariadb.constants.FIELD_FLAG* 8 | """ 9 | 10 | # Source: mariadb_com.h (MariaDB Connector(C) 11 | 12 | NOT_NULL = 1 13 | PRIMARY_KEY = 2 14 | UNIQUE_KEY = 4 15 | MULTIPLE_KEY = 8 16 | BLOB = 16 17 | UNSIGNED = 32 18 | ZEROFILL = 64 19 | BINARY = 128 20 | ENUM = 256 21 | AUTO_INCREMENT = 512 22 | TIMESTAMP = 1024 23 | SET = 2048 24 | NO_DEFAULT = 4096 25 | ON_UPDATE_NOW = 8192 26 | NUMERIC = 32768 27 | PART_OF_KEY = 16384 28 | GROUP = 32768 29 | UNIQUE = 65536 30 | -------------------------------------------------------------------------------- /mariadb/constants/FIELD_TYPE.py: -------------------------------------------------------------------------------- 1 | """ 2 | MariaDB FIELD_TYPE Constants 3 | 4 | These constants represent the field types supported by MariaDB. 5 | The field type is returned as second element of cursor description attribute. 6 | 7 | Field types are defined in module *mariadb.constants.FIELD_TYPE* 8 | """ 9 | 10 | DECIMAL = 0 11 | TINY = 1 12 | SHORT = 2 13 | LONG = 3 14 | FLOAT = 4 15 | DOUBLE = 5 16 | NULL = 6 17 | TIMESTAMP = 7 18 | LONGLONG = 8 19 | INT24 = 9 20 | DATE = 10 21 | TIME = 11 22 | DATETIME = 12 23 | YEAR = 13 24 | NEWDATE = 14 25 | VARCHAR = 15 26 | BIT = 16 27 | TIMESTAMP2 = 17 28 | DATETIME2 = 18 29 | TIME2 = 19 30 | JSON = 245 31 | NEWDECIMAL = 246 32 | ENUM = 247 33 | SET = 248 34 | TINY_BLOB = 249 35 | MEDIUM_BLOB = 250 36 | LONG_BLOB = 251 37 | BLOB = 252 38 | VAR_STRING = 253 39 | STRING = 254 40 | GEOMETRY = 255 41 | -------------------------------------------------------------------------------- /mariadb/constants/INDICATOR.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MariaDB indicator variables 3 | 4 | Indicator values are used in executemany() method of cursor class to 5 | indicate special values. 6 | ''' 7 | 8 | 9 | class MrdbIndicator(): 10 | indicator = 0 11 | 12 | def __init__(self, indicator): 13 | self.indicator = indicator 14 | 15 | 16 | NULL = MrdbIndicator(1) 17 | DEFAULT = MrdbIndicator(2) 18 | IGNORE = MrdbIndicator(3) 19 | IGNORE_ROW = MrdbIndicator(4) 20 | -------------------------------------------------------------------------------- /mariadb/constants/INFO.py: -------------------------------------------------------------------------------- 1 | """ 2 | Constants for _get_info method of MariadB connection object 3 | """ 4 | 5 | CHARSET_ID = 0 6 | CHARSET_NAME = 1 7 | CLIENT_ERRORS = 2 8 | CLIENT_VERSION = 3 9 | CLIENT_VERSION_ID = 4 10 | ASYNC_TIMEOUT = 5 11 | ASYNC_TIMEOUT_MS = 6 12 | CHARSET_INFO = 7 13 | ERROR = 8 14 | ERROR_ID = 9 15 | HOST = 10 16 | INFO = 11 17 | PORT = 12 18 | PROTOCOL_VERSION_ID = 13 19 | PVIO_TYPE = 14 20 | SCHEMA = 15 21 | SERVER_TYPE = 16 22 | SERVER_VERSION = 17 23 | SERVER_VERSION_ID = 18 24 | SOCKET = 19 25 | SQLSTATE = 20 26 | SSL_CIPHER = 21 27 | TLS_LIBRARY = 22 28 | TLS_VERSION = 23 29 | TLS_VERSION_ID = 24 30 | TYPE = 25 31 | UNIX_SOCKET = 26 32 | USER = 27 33 | MAX_ALLOWED_PACKET = 28 34 | NET_BUFFER_LENGTH = 29 35 | SERVER_STATUS = 30 36 | SERVER_CAPABILITIES = 31 37 | EXTENDED_SERVER_CAPABILITIES = 32 38 | CLIENT_CAPABILITIES = 33 39 | BYTES_READ = 34 40 | BYTES_SENT = 35 41 | TLS_PEER_CERT_INFO = 36 42 | TLS_VERIFY_STATUS = 37 43 | -------------------------------------------------------------------------------- /mariadb/constants/STATUS.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MariaDB status flags 3 | 4 | These flags describe the current status of the database server. 5 | ''' 6 | 7 | IN_TRANS = 1 8 | AUTOCOMMIT = 2 9 | MORE_RESULTS_EXIST = 8 10 | QUERY_NO_GOOD_INDEX_USED = 16 11 | QUERY_NO_INDEX_USED = 32 12 | CURSOR_EXISTS = 64 13 | LAST_ROW_SENT = 128 14 | DB_DROPPED = 256 15 | NO_BACKSLASH_ESCAPES = 512 16 | METADATA_CHANGED = 1024 17 | QUERY_WAS_SLOW = 2048 18 | PS_OUT_PARAMS = 4096 19 | IN_TRANS_READONLY = 8192 20 | SESSION_STATE_CHANGED = 16384 21 | ANSI_QUOTES = 32768 22 | -------------------------------------------------------------------------------- /mariadb/constants/TPC_STATE.py: -------------------------------------------------------------------------------- 1 | NONE = 0 2 | XID = 1 3 | PREPARE = 2 4 | -------------------------------------------------------------------------------- /mariadb/constants/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["CLIENT", "CURSOR", "FIELD_TYPE", "FIELD_FLAG", 2 | "INDICATOR", 'STATUS', 'ERR', 'CAPABILITY'] 3 | -------------------------------------------------------------------------------- /mariadb/dbapi20.py: -------------------------------------------------------------------------------- 1 | from mariadb.constants import FIELD_TYPE 2 | import time 3 | import datetime 4 | 5 | apilevel = '2.0' 6 | 7 | paramstyle = 'qmark' 8 | 9 | threadsafety = True 10 | 11 | 12 | class DbApiType(frozenset): 13 | """ 14 | Immutable set for type checking 15 | 16 | By default the following sets are defined: 17 | 18 | - BINARY: for binary field types 19 | - NUMBER: for numeric field types 20 | - STRING: for character based (string) field types 21 | - DATE: for date field type(s) 22 | - DATETIME: for datetime and timestamp field type(s) 23 | - TIME: for time field type(s) 24 | - TIMESTAMP: for datetime and timestamp field type(s) 25 | 26 | 27 | Example: 28 | >>> FIELD_TYPE.GEOMETRY == mariadb.BINARY 29 | True 30 | >>> FIELD_TYPE.FLOAT == mariadb.BINARY 31 | False 32 | """ 33 | 34 | def __eq__(self, field_type): 35 | if (isinstance(field_type, DbApiType)): 36 | return not self.difference(field_type) 37 | return field_type in self 38 | 39 | 40 | BINARY = DbApiType([FIELD_TYPE.GEOMETRY, 41 | FIELD_TYPE.LONG_BLOB, 42 | FIELD_TYPE.MEDIUM_BLOB, 43 | FIELD_TYPE.TINY_BLOB, 44 | FIELD_TYPE.BLOB]) 45 | 46 | STRING = DbApiType([FIELD_TYPE.ENUM, 47 | FIELD_TYPE.JSON, 48 | FIELD_TYPE.STRING, 49 | FIELD_TYPE.VARCHAR, 50 | FIELD_TYPE.VAR_STRING]) 51 | 52 | NUMBER = DbApiType([FIELD_TYPE.DECIMAL, 53 | FIELD_TYPE.DOUBLE, 54 | FIELD_TYPE.FLOAT, 55 | FIELD_TYPE.INT24, 56 | FIELD_TYPE.LONG, 57 | FIELD_TYPE.LONGLONG, 58 | FIELD_TYPE.NEWDECIMAL, 59 | FIELD_TYPE.SHORT, 60 | FIELD_TYPE.TINY, 61 | FIELD_TYPE.YEAR]) 62 | 63 | DATE = DbApiType([FIELD_TYPE.DATE]) 64 | TIME = DbApiType([FIELD_TYPE.TIME]) 65 | DATETIME = TIMESTAMP = DbApiType([FIELD_TYPE.DATETIME, 66 | FIELD_TYPE.TIMESTAMP]) 67 | ROWID = DbApiType() 68 | 69 | 70 | def Binary(object): 71 | """Constructs an object capable of holding a binary value.""" 72 | return bytes(object) 73 | 74 | 75 | def Date(year, month, day): 76 | """Constructs an object holding a date value.""" 77 | return datetime.date(year, month, day) 78 | 79 | 80 | def Time(hour, minute, second): 81 | """Constructs an object holding a time value.""" 82 | return datetime.time(hour, minute, second) 83 | 84 | 85 | def Timestamp(year, month, day, hour, minute, second): 86 | """Constructs an object holding a datetime value.""" 87 | return datetime.datetime(year, month, day, hour, minute, second) 88 | 89 | 90 | def DateFromTicks(ticks): 91 | """Constructs an object holding a date value from the given ticks value 92 | (number of seconds since the epoch). 93 | For more information see the documentation of the standard Python 94 | time module.""" 95 | return Date(*time.localtime(ticks)[:3]) 96 | 97 | 98 | def TimeFromTicks(ticks): 99 | """Constructs an object holding a time value from the given ticks value 100 | (number of seconds since the epoch). 101 | For more information see the documentation of the standard Python 102 | time module.""" 103 | return Time(*time.localtime(ticks)[3:6]) 104 | 105 | 106 | def TimestampFromTicks(ticks): 107 | """Constructs an object holding a datetime value from the given ticks value 108 | (number of seconds since the epoch). 109 | For more information see the documentation of the standard Python 110 | time module.""" 111 | return datetime.datetime(*time.localtime(ticks)[:6]) 112 | -------------------------------------------------------------------------------- /mariadb/field.py: -------------------------------------------------------------------------------- 1 | from mariadb.constants import FIELD_TYPE, FIELD_FLAG 2 | 3 | field_types = {FIELD_TYPE.DECIMAL: "DECIMAL", 4 | FIELD_TYPE.TINY: "TINY", 5 | FIELD_TYPE.SHORT: "SHORT", 6 | FIELD_TYPE.LONG: "LONG", 7 | FIELD_TYPE.FLOAT: "FLOAT", 8 | FIELD_TYPE.DOUBLE: "DOUBLE", 9 | FIELD_TYPE.NULL: "NULL", 10 | FIELD_TYPE.TIMESTAMP: "TIMESTAMP", 11 | FIELD_TYPE.LONGLONG: "LONGLONG", 12 | FIELD_TYPE.INT24: "INT24", 13 | FIELD_TYPE.DATE: "DATE", 14 | FIELD_TYPE.TIME: "TIME", 15 | FIELD_TYPE.DATETIME: "DATETIME", 16 | FIELD_TYPE.YEAR: "YEAR", 17 | FIELD_TYPE.NEWDATE: "NEWDATE", 18 | FIELD_TYPE.VARCHAR: "VARCHAR", 19 | FIELD_TYPE.BIT: "BIT", 20 | FIELD_TYPE.JSON: "JSON", 21 | FIELD_TYPE.NEWDECIMAL: "NEWDECIMAL", 22 | FIELD_TYPE.ENUM: "ENUM", 23 | FIELD_TYPE.SET: "SET", 24 | FIELD_TYPE.TINY_BLOB: "TINY_BLOB", 25 | FIELD_TYPE.MEDIUM_BLOB: "MEDIUM_BLOB", 26 | FIELD_TYPE.LONG_BLOB: "LONG_BLOB", 27 | FIELD_TYPE.BLOB: "BLOB", 28 | FIELD_TYPE.VAR_STRING: "VAR_STRING", 29 | FIELD_TYPE.STRING: "STRING", 30 | FIELD_TYPE.GEOMETRY: "GEOMETRY"} 31 | 32 | field_flags = {FIELD_FLAG.NOT_NULL: "NOT_NULL", 33 | FIELD_FLAG.PRIMARY_KEY: "PRIMARY_KEY", 34 | FIELD_FLAG.UNIQUE_KEY: "UNIQUE_KEY", 35 | FIELD_FLAG.MULTIPLE_KEY: "PART_KEY", 36 | FIELD_FLAG.BLOB: "BLOB", 37 | FIELD_FLAG.UNSIGNED: "UNSIGNED", 38 | FIELD_FLAG.ZEROFILL: "ZEROFILL", 39 | FIELD_FLAG.BINARY: "BINARY", 40 | FIELD_FLAG.ENUM: "NUMERIC", 41 | FIELD_FLAG.AUTO_INCREMENT: "AUTO_INCREMENT", 42 | FIELD_FLAG.TIMESTAMP: "TIMESTAMP", 43 | FIELD_FLAG.SET: "SET", 44 | FIELD_FLAG.NO_DEFAULT: "NO_DEFAULT", 45 | FIELD_FLAG.ON_UPDATE_NOW: "UPDATE_TIMESTAMP", 46 | FIELD_FLAG.NUMERIC: "NUMERIC"} 47 | 48 | 49 | class fieldinfo(): 50 | 51 | def type(self, description): 52 | if description[1] in field_types: 53 | return field_types[description[1]] 54 | return None 55 | 56 | def flag(self, description): 57 | flags = [field_flags[f] for f in field_flags.keys() 58 | if description[7] & f] 59 | return " | ".join(flags) 60 | -------------------------------------------------------------------------------- /mariadb/mariadb.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2018-2020 Georg Richter and MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | ******************************************************************************/ 19 | #define MARIADB_CONNECTION 20 | 21 | #include "mariadb_python.h" 22 | #include "docs/module.h" 23 | #include "docs/exception.h" 24 | #include 25 | #include 26 | 27 | #ifdef __clang__ 28 | # if defined(__has_feature) && __has_feature(address_sanitizer) 29 | # define HAVE_ASAN Py_True 30 | # else 31 | # define HAVE_ASAN Py_False 32 | # endif 33 | #elif defined(__GNUC__) 34 | # ifdef __SANITIZE_ADDRESS__ 35 | # define HAVE_ASAN Py_True 36 | # else 37 | # define HAVE_ASAN Py_False 38 | # endif 39 | #else 40 | # define HAVE_ASAN Py_False 41 | #endif 42 | 43 | extern int codecs_datetime_init(void); 44 | extern int connection_datetime_init(void); 45 | 46 | PyObject *decimal_module= NULL, 47 | *decimal_type= NULL, 48 | *socket_module= NULL, 49 | *indicator_module= NULL; 50 | extern uint16_t max_pool_size; 51 | 52 | int 53 | Mariadb_traverse(PyObject *self, 54 | visitproc visit, 55 | void *arg) 56 | { 57 | return 0; 58 | } 59 | 60 | static PyMethodDef 61 | Mariadb_Methods[] = 62 | { 63 | /* PEP-249: mandatory */ 64 | {"connect", (PyCFunction)MrdbConnection_connect, 65 | METH_VARARGS | METH_KEYWORDS, 66 | module_connect__doc__}, 67 | /* Todo: add methods for api functions which don't require 68 | a connection */ 69 | {NULL} /* always last */ 70 | }; 71 | 72 | /* MariaDB module definition */ 73 | static struct PyModuleDef 74 | mariadb_module= { 75 | PyModuleDef_HEAD_INIT, 76 | "_mariadb", 77 | "MariaDB Connector for Python", 78 | -1, 79 | Mariadb_Methods 80 | }; 81 | 82 | static int mariadb_datetime_init(void) 83 | { 84 | PyDateTime_IMPORT; 85 | 86 | if (!PyDateTimeAPI) { 87 | PyErr_SetString(PyExc_ImportError, "DateTimeAPI initialization failed"); 88 | return 1; 89 | } 90 | return 0; 91 | } 92 | 93 | static void mariadb_add_exception(PyObject *module, 94 | PyObject **exception, 95 | const char *exception_name, 96 | PyObject *base_exception, 97 | const char *doc, 98 | const char *object_name) 99 | { 100 | *exception= PyErr_NewExceptionWithDoc(exception_name, 101 | doc, 102 | Mariadb_Error, 103 | NULL); 104 | 105 | Py_INCREF(*exception); 106 | PyModule_AddObject(module, object_name, *exception); 107 | } 108 | 109 | /* MariaDB module initialization function */ 110 | PyMODINIT_FUNC PyInit__mariadb(void) 111 | { 112 | PyObject *module= PyModule_Create(&mariadb_module); 113 | 114 | /* check if client library is compatible */ 115 | if (mysql_get_client_version() < MARIADB_PACKAGE_VERSION_ID) 116 | { 117 | char errmsg[255]; 118 | 119 | snprintf(errmsg, 254, "MariaDB Connector/Python was build with MariaDB Connector/C %s, " 120 | "while the loaded MariaDB Connector/C library has version %s.", 121 | MARIADB_PACKAGE_VERSION, mysql_get_client_info()); 122 | PyErr_SetString(PyExc_ImportError, errmsg); 123 | goto error; 124 | } 125 | 126 | /* Initialize DateTimeAPI */ 127 | if (mariadb_datetime_init() || 128 | connection_datetime_init() || 129 | codecs_datetime_init()) 130 | { 131 | goto error; 132 | } 133 | 134 | Py_SET_TYPE(&MrdbConnection_Type, &PyType_Type); 135 | if (PyType_Ready(&MrdbConnection_Type) == -1) 136 | { 137 | goto error; 138 | } 139 | 140 | /* Import Decimal support (CONPY-49) */ 141 | if (!(decimal_module= PyImport_ImportModule("decimal")) || 142 | !(decimal_type= PyObject_GetAttr(decimal_module, PyUnicode_FromString("Decimal")))) 143 | { 144 | goto error; 145 | } 146 | 147 | if (!(socket_module= PyImport_ImportModule("socket"))) 148 | { 149 | goto error; 150 | } 151 | 152 | Py_SET_TYPE(&MrdbCursor_Type, &PyType_Type); 153 | if (PyType_Ready(&MrdbCursor_Type) == -1) 154 | { 155 | goto error; 156 | } 157 | PyModule_AddObject(module, "cursor", (PyObject *)&MrdbCursor_Type); 158 | 159 | /* optional (MariaDB specific) globals */ 160 | PyModule_AddObject(module, "mariadbapi_version", 161 | PyUnicode_FromString(mysql_get_client_info())); 162 | 163 | Mariadb_Error= PyErr_NewException("mariadb.Error", 164 | PyExc_Exception, 165 | NULL); 166 | Py_INCREF(Mariadb_Error); 167 | PyModule_AddObject(module, "Error", Mariadb_Error); 168 | 169 | mariadb_add_exception(module, &Mariadb_InterfaceError, 170 | "mariadb.InterfaceError", Mariadb_Error, 171 | exception_interface__doc__, "InterfaceError"); 172 | mariadb_add_exception(module, &Mariadb_DatabaseError, 173 | "mariadb.DatabaseError", Mariadb_Error, 174 | exception_database__doc__, "DatabaseError"); 175 | mariadb_add_exception(module, &Mariadb_OperationalError, 176 | "mariadb.OperationalError", Mariadb_Error, 177 | exception_operational__doc__, "OperationalError"); 178 | mariadb_add_exception(module, &Mariadb_Warning, 179 | "mariadb.Warning", NULL, exception_warning__doc__, "Warning"); 180 | mariadb_add_exception(module, &Mariadb_IntegrityError, 181 | "mariadb.IntegrityError", Mariadb_Error, 182 | exception_integrity__doc__, "IntegrityError"); 183 | mariadb_add_exception(module, &Mariadb_InternalError, 184 | "mariadb.InternalError", Mariadb_Error, 185 | exception_internal__doc__, "InternalError"); 186 | mariadb_add_exception(module, &Mariadb_ProgrammingError, 187 | "mariadb.ProgrammingError", Mariadb_Error, 188 | exception_programming__doc__, "ProgrammingError"); 189 | mariadb_add_exception(module, &Mariadb_NotSupportedError, 190 | "mariadb.NotSupportedError", Mariadb_Error, 191 | exception_notsupported__doc__, "NotSupportedError"); 192 | mariadb_add_exception(module, &Mariadb_DataError, 193 | "mariadb.DataError", Mariadb_DatabaseError, 194 | exception_data__doc__, "DataError"); 195 | mariadb_add_exception(module, &Mariadb_PoolError, 196 | "mariadb.PoolError", Mariadb_Error, 197 | exception_pool__doc__, "PoolError"); 198 | 199 | Py_INCREF(&MrdbConnection_Type); 200 | PyModule_AddObject(module, "connection", (PyObject *)&MrdbConnection_Type); 201 | PyModule_AddObject(module, "_have_asan", HAVE_ASAN); 202 | Py_INCREF(HAVE_ASAN); 203 | 204 | return module; 205 | error: 206 | if (PyErr_Occurred()) 207 | { 208 | return NULL; 209 | } 210 | PyErr_SetString(PyExc_ImportError, "Mariadb module initialization failed."); 211 | return NULL; 212 | } 213 | -------------------------------------------------------------------------------- /mariadb/mariadb_exception.c: -------------------------------------------------------------------------------- 1 | /************************************************************************************ 2 | Copyright (C) 2018 Georg Richter and MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 | *************************************************************************************/ 19 | 20 | #include 21 | #include 22 | 23 | /* Exceptions */ 24 | PyObject *Mariadb_InterfaceError; 25 | PyObject *Mariadb_Error; 26 | PyObject *Mariadb_DatabaseError; 27 | PyObject *Mariadb_DataError; 28 | PyObject *Mariadb_PoolError; 29 | PyObject *Mariadb_OperationalError; 30 | PyObject *Mariadb_IntegrityError; 31 | PyObject *Mariadb_InternalError; 32 | PyObject *Mariadb_ProgrammingError; 33 | PyObject *Mariadb_NotSupportedError; 34 | PyObject *Mariadb_Warning; 35 | 36 | struct st_error_map { 37 | char sqlstate[3]; 38 | uint8_t type; 39 | }; 40 | 41 | static PyObject *get_exception_type(int error_number) 42 | { 43 | /* This list might be incomplete, special error values which are 44 | not handled yet will be returned as Internal or Operational errors. 45 | error codes are defined in errmsg.h (client errors) and mysqld_error.h 46 | (server errors) */ 47 | switch (error_number) { 48 | /* InterfaceError */ 49 | case 0: 50 | case CR_SERVER_LOST: 51 | case CR_SERVER_GONE_ERROR: 52 | case CR_SERVER_HANDSHAKE_ERR: 53 | case CR_IPSOCK_ERROR: 54 | case CR_COMMANDS_OUT_OF_SYNC: 55 | return Mariadb_InterfaceError; 56 | /* DataError: Exception raised for errors that are due to problems with the processed 57 | data like division by zero, numeric value out of range, etc */ 58 | case ER_DATA_TOO_LONG: 59 | case ER_DATETIME_FUNCTION_OVERFLOW: 60 | case ER_DIVISION_BY_ZERO: 61 | case ER_NO_DEFAULT: 62 | case ER_PRIMARY_CANT_HAVE_NULL: 63 | case ER_WARN_DATA_OUT_OF_RANGE: 64 | case WARN_DATA_TRUNCATED: 65 | return Mariadb_DataError; 66 | 67 | /* ProgrammingError: Exception raised for programming errors, e.g. table not found or 68 | already exists, syntax error in the SQL statement, wrong number of parameters specified, etc. */ 69 | case ER_EMPTY_QUERY: 70 | case ER_CANT_DO_THIS_DURING_AN_TRANSACTION: 71 | case ER_DB_CREATE_EXISTS: 72 | case ER_FIELD_SPECIFIED_TWICE: 73 | case ER_INVALID_GROUP_FUNC_USE: 74 | case ER_NO_SUCH_INDEX: 75 | case ER_NO_SUCH_KEY_VALUE: 76 | case ER_NO_SUCH_TABLE: 77 | case ER_NO_SUCH_USER: 78 | case ER_PARSE_ERROR: 79 | case ER_SYNTAX_ERROR: 80 | case ER_TABLE_MUST_HAVE_COLUMNS: 81 | case ER_UNSUPPORTED_EXTENSION: 82 | case ER_WRONG_DB_NAME: 83 | case ER_WRONG_TABLE_NAME: 84 | case ER_BAD_DB_ERROR: 85 | case ER_BAD_FIELD_ERROR: 86 | return Mariadb_ProgrammingError; 87 | 88 | /* IntegrityError: Exception raised when the relational integrity of the database is affected, 89 | e.g. a foreign key check fails */ 90 | case ER_CANNOT_ADD_FOREIGN: 91 | case ER_DUP_ENTRY: 92 | case ER_DUP_UNIQUE: 93 | case ER_NO_DEFAULT_FOR_FIELD: 94 | case ER_NO_REFERENCED_ROW: 95 | case ER_NO_REFERENCED_ROW_2: 96 | case ER_ROW_IS_REFERENCED: 97 | case ER_ROW_IS_REFERENCED_2: 98 | case ER_XAER_OUTSIDE: 99 | case ER_XAER_RMERR: 100 | case ER_BAD_NULL_ERROR: 101 | case ER_DATA_OUT_OF_RANGE: 102 | case ER_CONSTRAINT_FAILED: 103 | case ER_DUP_CONSTRAINT_NAME: 104 | return Mariadb_IntegrityError; 105 | default: 106 | /* MariaDB Error */ 107 | if (error_number >= 1000) 108 | return Mariadb_OperationalError; 109 | /* same behavior as in MySQLdb: we return an InternalError, in case of system errors */ 110 | return Mariadb_InternalError; 111 | } 112 | 113 | return NULL; 114 | } 115 | 116 | void mariadb_exception_connection_gone(PyObject *exception_type, 117 | int error_no, 118 | const char *message, 119 | ...) 120 | { 121 | va_list ap; 122 | PyObject *ErrorMsg= 0; 123 | PyObject *ErrorNo= 0; 124 | PyObject *SqlState= 0; 125 | PyObject *Exception= 0; 126 | 127 | 128 | ErrorNo= PyLong_FromLong(CR_UNKNOWN_ERROR); 129 | SqlState= PyUnicode_FromString("HY000"); 130 | va_start(ap, message); 131 | ErrorMsg= PyUnicode_FromFormatV(message, ap); 132 | va_end(ap); 133 | 134 | if (!(Exception= PyObject_CallFunctionObjArgs(exception_type, ErrorMsg, NULL))) 135 | { 136 | PyErr_SetString(PyExc_RuntimeError, 137 | "Failed to create exception"); 138 | return; 139 | } 140 | 141 | PyObject_SetAttr(Exception, PyUnicode_FromString("sqlstate"), SqlState); 142 | PyObject_SetAttr(Exception, PyUnicode_FromString("errno"), ErrorNo); 143 | PyObject_SetAttr(Exception, PyUnicode_FromString("errmsg"), ErrorMsg); 144 | /* For MySQL Connector/Python compatibility */ 145 | PyObject_SetAttr(Exception, PyUnicode_FromString("msg"), ErrorMsg); 146 | PyErr_SetObject(exception_type, Exception); 147 | Py_XDECREF(ErrorMsg); 148 | Py_XDECREF(ErrorNo); 149 | Py_XDECREF(SqlState); 150 | } 151 | 152 | /** 153 | mariadb_throw_exception() 154 | @brief raises an exception 155 | 156 | @param handle[in] a connection or statement handle 157 | @param exception_type[in] type of exception 158 | @param handle_type[in] -1 no handle (use error_no) 159 | 0 MYSQL 160 | 1 MYSQL_STMT 161 | @param message[in] Error message. If message is NULL, the error 162 | message will be retrieved from specified handle. 163 | @param ... [in] message parameter 164 | 165 | @return void 166 | 167 | */ 168 | void mariadb_throw_exception(void *handle, 169 | PyObject *exception_type, 170 | int8_t is_statement, 171 | const char *message, 172 | ...) 173 | { 174 | va_list ap; 175 | PyObject *ErrorMsg= 0; 176 | PyObject *ErrorNo= 0; 177 | PyObject *SqlState= 0; 178 | PyObject *Exception= 0; 179 | 180 | if (message) 181 | { 182 | ErrorNo= PyLong_FromLong(CR_UNKNOWN_ERROR); 183 | SqlState= PyUnicode_FromString("HY000"); 184 | va_start(ap, message); 185 | ErrorMsg= PyUnicode_FromFormatV(message, ap); 186 | va_end(ap); 187 | } else 188 | { 189 | exception_type= get_exception_type(is_statement ? mysql_stmt_errno((MYSQL_STMT*) handle) : mysql_errno((MYSQL *)handle)); 190 | 191 | if (!exception_type) 192 | exception_type= Mariadb_DatabaseError; 193 | 194 | ErrorNo= PyLong_FromLong(is_statement ? 195 | mysql_stmt_errno((MYSQL_STMT *)handle) : mysql_errno((MYSQL *)handle)); 196 | ErrorMsg= PyUnicode_FromString(is_statement ? 197 | mysql_stmt_error((MYSQL_STMT *)handle) : mysql_error((MYSQL *)handle)); 198 | SqlState= PyUnicode_FromString(is_statement ? 199 | mysql_stmt_sqlstate((MYSQL_STMT *)handle) : mysql_sqlstate((MYSQL *)handle)); 200 | } 201 | 202 | if (!(Exception= PyObject_CallFunctionObjArgs(exception_type, ErrorMsg, NULL))) 203 | { 204 | PyErr_SetString(PyExc_RuntimeError, 205 | "Failed to create exception"); 206 | return; 207 | } 208 | 209 | PyObject_SetAttr(Exception, PyUnicode_FromString("sqlstate"), SqlState); 210 | PyObject_SetAttr(Exception, PyUnicode_FromString("errno"), ErrorNo); 211 | PyObject_SetAttr(Exception, PyUnicode_FromString("errmsg"), ErrorMsg); 212 | /* For MySQL Connector/Python compatibility */ 213 | PyObject_SetAttr(Exception, PyUnicode_FromString("msg"), ErrorMsg); 214 | PyErr_SetObject(exception_type, Exception); 215 | Py_XDECREF(ErrorMsg); 216 | Py_XDECREF(ErrorNo); 217 | Py_XDECREF(SqlState); 218 | } 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /mariadb_posix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | from packaging import version 5 | import sys 6 | import os 7 | 8 | 9 | class MariaDBConfiguration(): 10 | lib_dirs = [] 11 | libs = [] 12 | version = [] 13 | includes = [] 14 | extra_objects = [] 15 | extra_compile_args = [] 16 | extra_link_args = [] 17 | 18 | 19 | def mariadb_config(config, option): 20 | from os import popen 21 | file = popen("%s --%s" % (config, option)) 22 | data = file.read().strip().split() 23 | rc = file.close() 24 | if rc: 25 | if rc / 256: 26 | data = [] 27 | if rc / 256 > 1: 28 | raise EnvironmentError( 29 | """mariadb_config not found. 30 | 31 | This error typically indicates that MariaDB Connector/C, a dependency which 32 | must be preinstalled, is not found. 33 | If MariaDB Connector/C is not installed, see installation instructions 34 | If MariaDB Connector/C is installed, either set the environment variable 35 | MARIADB_CONFIG or edit the configuration file 'site.cfg' to set the 36 | 'mariadb_config' option to the file location of the mariadb_config utility. 37 | """) 38 | 39 | return data 40 | 41 | 42 | def dequote(s): 43 | if s[0] in "\"'" and s[0] == s[-1]: 44 | s = s[1:-1] 45 | return s 46 | 47 | 48 | def get_config(options): 49 | required_version = "3.3.1" 50 | static = options["link_static"] 51 | 52 | try: 53 | try: 54 | config_prg = os.environ["MARIADB_CONFIG"] 55 | except KeyError: 56 | config_prg = options["mariadb_config"] 57 | subprocess.call([config_prg, "--cc_version"]) 58 | except FileNotFoundError: 59 | # using default from path 60 | config_prg = "mariadb_config" 61 | 62 | cc_version = mariadb_config(config_prg, "cc_version") 63 | if version.Version(cc_version[0]) < version.Version(required_version): 64 | print('MariaDB Connector/Python requires MariaDB Connector/C ' 65 | '>= %s, found version %s' % (required_version, cc_version[0])) 66 | sys.exit(2) 67 | cfg = MariaDBConfiguration() 68 | cfg.version = cc_version[0] 69 | 70 | plugindir = mariadb_config(config_prg, "plugindir") 71 | libs = mariadb_config(config_prg, "libs") 72 | extra_libs = mariadb_config(config_prg, "libs_sys") 73 | cfg.lib_dirs = [dequote(i[2:]) for i in libs if i.startswith("-L")] 74 | 75 | cfg.libs = [dequote(i[2:]) for i in libs if i.startswith("-l")] 76 | includes = mariadb_config(config_prg, "include") 77 | mariadb_includes = [dequote(i[2:]) for i in includes if i.startswith("-I")] 78 | mariadb_includes.extend(["./include"]) 79 | if static.lower() == "on": 80 | cfg.extra_link_args = ["-u mysql_ps_fetch_functions"] 81 | cfg.extra_objects = ['{}/lib{}.a'.format(cfg.lib_dirs[0], lib) 82 | for lib in ["mariadbclient"]] 83 | cfg.libs = [dequote(i[2:]) 84 | for i in extra_libs if i.startswith("-l")] 85 | cfg.includes = mariadb_includes 86 | cfg.extra_compile_args = ["-DDEFAULT_PLUGINS_SUBDIR=\"%s\"" % plugindir[0]] 87 | return cfg 88 | -------------------------------------------------------------------------------- /mariadb_windows.py: -------------------------------------------------------------------------------- 1 | # 2 | # Windows configuration 3 | # 4 | 5 | import os 6 | import platform 7 | import sys 8 | from packaging import version 9 | 10 | from winreg import ConnectRegistry, OpenKey, QueryValueEx,\ 11 | HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY 12 | 13 | 14 | class MariaDBConfiguration(): 15 | lib_dirs = [] 16 | libs = [] 17 | version = [] 18 | includes = [] 19 | extra_objects = [] 20 | extra_compile_args = [] 21 | extra_link_args = [] 22 | 23 | 24 | def get_config(options): 25 | static = options["link_static"] 26 | mariadb_dir = options["install_dir"] 27 | required_version = "3.2.4" 28 | 29 | if not os.path.exists(mariadb_dir): 30 | try: 31 | mariadb_dir = os.environ["MARIADB_CC_INSTALL_DIR"] 32 | cc_version = ["", ""] 33 | print("using environment configuration " + mariadb_dir) 34 | except KeyError: 35 | 36 | try: 37 | local_reg = ConnectRegistry(None, HKEY_LOCAL_MACHINE) 38 | if platform.architecture()[0] == '32bit': 39 | connector_key = OpenKey(local_reg, 40 | 'SOFTWARE\\MariaDB Corporation\\' 41 | 'MariaDB Connector C') 42 | else: 43 | connector_key = OpenKey(local_reg, 44 | 'SOFTWARE\\MariaDB Corporation\\' 45 | 'MariaDB Connector C 64-bit', 46 | access=KEY_READ | KEY_WOW64_64KEY) 47 | cc_version = QueryValueEx(connector_key, "Version") 48 | if (version.Version(cc_version[0]) < 49 | version.Version(required_version)): 50 | print("MariaDB Connector/Python requires " 51 | "MariaDB Connector/C " 52 | ">= %s (found version: %s") \ 53 | % (required_version, cc_version[0]) 54 | sys.exit(2) 55 | mariadb_dir = QueryValueEx(connector_key, "InstallDir")[0] 56 | 57 | except Exception: 58 | print("Could not find InstallationDir of MariaDB Connector/C. " 59 | "Please make sure MariaDB Connector/C is installed or " 60 | "specify the InstallationDir of MariaDB Connector/C by " 61 | "setting the environment variable " 62 | "MARIADB_CC_INSTALL_DIR.") 63 | sys.exit(3) 64 | 65 | print("Found MariaDB Connector/C in '%s'" % mariadb_dir) 66 | cfg = MariaDBConfiguration() 67 | cfg.includes = [".\\include", mariadb_dir + "\\include", mariadb_dir + 68 | "\\include\\mysql", mariadb_dir + "\\include\\mariadb", 69 | mariadb_dir + "\\include\\mariadb\\mysql"] 70 | cfg.lib_dirs = [mariadb_dir + "\\lib", mariadb_dir + "\\lib\\mariadb"] 71 | cfg.libs = ["ws2_32", "advapi32", "kernel32", "shlwapi", "crypt32", 72 | "secur32", "bcrypt"] 73 | if static.lower() == "on" or static.lower() == "default": 74 | cfg.libs.append("mariadbclient") 75 | else: 76 | print("dynamic") 77 | cfg.extra_link_args = ["/NODEFAULTLIB:LIBCMT"] 78 | cfg.extra_compile_args = ["/MD"] 79 | 80 | f = open("./include/config_win.h", "w") 81 | f.write("#define DEFAULT_PLUGINS_SUBDIR \"%s\\\\lib\\\\mariadb\\\\plugin\"" % 82 | mariadb_dir.replace(""'\\', '\\\\')) 83 | f.close() 84 | return cfg 85 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "wheel", 4 | "setuptools", 5 | "packaging", 6 | ] 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | from setuptools import setup, Extension 6 | from configparser import ConfigParser 7 | 8 | # read the contents of your README file 9 | from os import path 10 | 11 | if os.name == "posix": 12 | from mariadb_posix import get_config 13 | if os.name == "nt": 14 | from mariadb_windows import get_config # noqa: F811 15 | 16 | this_directory = path.abspath(path.dirname(__file__)) 17 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: 18 | long_description = f.read() 19 | 20 | define_macros = [] 21 | 22 | # read settings from site.cfg 23 | c = ConfigParser() 24 | c.read(['site.cfg']) 25 | options = dict(c.items('cc_options')) 26 | 27 | cfg = get_config(options) 28 | 29 | PY_MARIADB_AUTHORS = "Georg Richter" 30 | 31 | PY_MARIADB_MAJOR_VERSION = 1 32 | PY_MARIADB_MINOR_VERSION = 1 33 | PY_MARIADB_PATCH_VERSION = 13 34 | PY_MARIADB_PRE_RELEASE_SEGMENT = None 35 | PY_MARIADB_PRE_RELEASE_NR = 0 36 | PY_MARIADB_POST_RELEASE_SEGMENT = None 37 | PY_MARIADB_POST_RELEASE_NR = 0 38 | 39 | PY_MARIADB_VERSION = "%s.%s.%s" % (PY_MARIADB_MAJOR_VERSION, 40 | PY_MARIADB_MINOR_VERSION, 41 | PY_MARIADB_PATCH_VERSION) 42 | 43 | if PY_MARIADB_POST_RELEASE_SEGMENT: 44 | PY_MARIADB_VERSION += ".%s" % (PY_MARIADB_POST_RELEASE_SEGMENT + 45 | PY_MARIADB_POST_RELEASE_NR) 46 | 47 | PY_MARIADB_VERSION_INFO = (PY_MARIADB_MAJOR_VERSION, 48 | PY_MARIADB_MINOR_VERSION, 49 | PY_MARIADB_PATCH_VERSION) 50 | 51 | if PY_MARIADB_PRE_RELEASE_SEGMENT: 52 | PY_MARIADB_VERSION_INFO = PY_MARIADB_VERSION_INFO + ( 53 | PY_MARIADB_PRE_RELEASE_SEGMENT, 54 | PY_MARIADB_PRE_RELEASE_NR) 55 | 56 | if PY_MARIADB_POST_RELEASE_SEGMENT: 57 | PY_MARIADB_VERSION_INFO = PY_MARIADB_VERSION_INFO + ( 58 | PY_MARIADB_POST_RELEASE_SEGMENT, 59 | PY_MARIADB_POST_RELEASE_NR) 60 | 61 | define_macros.append(("PY_MARIADB_MAJOR_VERSION", PY_MARIADB_MAJOR_VERSION)) 62 | define_macros.append(("PY_MARIADB_MINOR_VERSION", PY_MARIADB_MINOR_VERSION)) 63 | define_macros.append(("PY_MARIADB_PATCH_VERSION", PY_MARIADB_PATCH_VERSION)) 64 | define_macros.append(("PY_MARIADB_PRE_RELEASE_SEGMENT", "\"%s\"" % 65 | PY_MARIADB_PRE_RELEASE_SEGMENT)) 66 | define_macros.append(("PY_MARIADB_PRE_RELEASE_NR", "\"%s\"" % 67 | PY_MARIADB_PRE_RELEASE_NR)) 68 | define_macros.append(("PY_MARIADB_POST_RELEASE_SEGMENT", "\"%s\"" % 69 | PY_MARIADB_POST_RELEASE_SEGMENT)) 70 | define_macros.append(("PY_MARIADB_POST_RELEASE_NR", "\"%s\"" % 71 | PY_MARIADB_POST_RELEASE_NR)) 72 | 73 | 74 | with open("mariadb/release_info.py", "w") as rel_info: 75 | rel_info.write("__author__ = '%s'\n__version__ = '%s'\n__version_info__" 76 | " = %s\n" % 77 | (PY_MARIADB_AUTHORS, PY_MARIADB_VERSION, 78 | PY_MARIADB_VERSION_INFO)) 79 | 80 | setup(name='mariadb', 81 | version=PY_MARIADB_VERSION, 82 | python_requires='>=3.8', 83 | classifiers=[ 84 | 'Development Status :: 5 - Production/Stable', 85 | 'Environment :: Console', 86 | 'Environment :: MacOS X', 87 | 'Environment :: Win32 (MS Windows)', 88 | 'License :: OSI Approved :: GNU Lesser General Public License' 89 | ' v2 or later (LGPLv2+)', 90 | 'Programming Language :: C', 91 | 'Programming Language :: Python', 92 | 'Programming Language :: Python :: 3.9', 93 | 'Programming Language :: Python :: 3.10', 94 | 'Programming Language :: Python :: 3.11', 95 | 'Programming Language :: Python :: 3.12', 96 | 'Programming Language :: Python :: 3.13', 97 | 'Operating System :: Microsoft :: Windows', 98 | 'Operating System :: MacOS', 99 | 'Operating System :: POSIX', 100 | 'Intended Audience :: End Users/Desktop', 101 | 'Intended Audience :: Developers', 102 | 'Intended Audience :: System Administrators', 103 | 'Topic :: Database' 104 | ], 105 | description='Python MariaDB extension', 106 | long_description=long_description, 107 | long_description_content_type='text/markdown', 108 | author=PY_MARIADB_AUTHORS, 109 | license='LGPL 2.1', 110 | url='https://www.github.com/mariadb-corporation/' 111 | 'mariadb-connector-python', 112 | project_urls={ 113 | "Bug Tracker": "https://jira.mariadb.org/", 114 | "Documentation": "https://mariadb-corporation.github.io/" 115 | "mariadb-connector-python/", 116 | "Source Code": "https://www.github.com/mariadb-corporation/" 117 | "mariadb-connector-python", 118 | }, 119 | install_requires=['packaging'], 120 | ext_modules=[Extension('mariadb._mariadb', 121 | ['mariadb/mariadb.c', 122 | 'mariadb/mariadb_codecs.c', 123 | 'mariadb/mariadb_connection.c', 124 | 'mariadb/mariadb_cursor.c', 125 | 'mariadb/mariadb_exception.c', 126 | 'mariadb/mariadb_parser.c'], 127 | define_macros=define_macros, 128 | include_dirs=cfg.includes, 129 | library_dirs=cfg.lib_dirs, 130 | libraries=cfg.libs, 131 | extra_compile_args=cfg.extra_compile_args, 132 | extra_link_args=cfg.extra_link_args, 133 | extra_objects=cfg.extra_objects 134 | )], 135 | py_modules=['mariadb.__init__', 136 | 'mariadb.connectionpool', 137 | 'mariadb.connections', 138 | 'mariadb.constants.CAPABILITY', 139 | 'mariadb.constants.CLIENT', 140 | 'mariadb.constants.CURSOR', 141 | 'mariadb.constants.ERR', 142 | 'mariadb.constants.FIELD_FLAG', 143 | 'mariadb.constants.FIELD_TYPE', 144 | 'mariadb.constants.EXT_FIELD_TYPE', 145 | 'mariadb.constants.INDICATOR', 146 | 'mariadb.constants.INFO', 147 | 'mariadb.constants.STATUS', 148 | 'mariadb.constants.TPC_STATE', 149 | 'mariadb.cursors', 150 | 'mariadb.dbapi20', 151 | 'mariadb.field', 152 | 'mariadb.release_info']) 153 | -------------------------------------------------------------------------------- /site.cfg: -------------------------------------------------------------------------------- 1 | # configuration file for building MariaDB Connector/C Python 2 | [cc_options] 3 | # static or dynamic linking 4 | link_static=default 5 | # Windows: location of MySQL Connector/C installation 6 | install_dir=c:\Program Files\MariaDB\MariaDB Connector C 64-bit 7 | #install_dir=c:\Program Files (x86)\MariaDB\MariaDB Connector C 8 | # Posix: location of mariadb_config executable 9 | mariadb_config=/usr/local/bin/mariadb_config 10 | -------------------------------------------------------------------------------- /testing/bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | # requirement: pip install pyperf 5 | 6 | import importlib 7 | 8 | from benchmarks.internal_bench import test_suite 9 | from benchmarks.internal_bench import run_test 10 | 11 | from test.conf_test import conf, glob 12 | 13 | module = glob() 14 | dbdrv = importlib.import_module(module["module"]) 15 | 16 | 17 | def main(): 18 | default_conf = conf() 19 | conn = dbdrv.connect(**default_conf) 20 | run_test(test_suite(dbdrv.paramstyle), conn, dbdrv.paramstyle) 21 | conn.close() 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /testing/bench_init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | # requirement: pip install pyperf 5 | 6 | import importlib 7 | 8 | from test.conf_test import conf, glob 9 | from benchmarks.setup_db import init_db 10 | 11 | module = glob() 12 | dbdrv = importlib.import_module(module["module"]) 13 | 14 | 15 | def main(): 16 | default_conf = conf() 17 | conn = dbdrv.connect(**default_conf) 18 | init_db(conn, dbdrv.paramstyle) 19 | conn.close() 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /testing/benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | ``` 4 | pip install mysql-connector-python pyperf 5 | python bench_mariadb.py -o mariadb_bench.json --inherit-environ=TEST_USER,TEST_HOST,TEST_PORT 6 | python bench_mysql.py -o mysql_bench.json --inherit-environ=TEST_USER,TEST_HOST,TEST_PORT 7 | ``` 8 | 9 | Results are available to pyperf json format 10 | 11 | An example of 12 | ``` 13 | >python -m pyperf compare_to mysql_bench.json mariadb_bench.json --table 14 | +----------------------------------------------------+-------------+------------------------------+ 15 | | Benchmark | mysql_bench | mariadb_bench | 16 | +====================================================+=============+==============================+ 17 | | do 1 | 114 us | 45.4 us: 2.50x faster (-60%) | 18 | +----------------------------------------------------+-------------+------------------------------+ 19 | | select 1 | 209 us | 57.3 us: 3.65x faster (-73%) | 20 | +----------------------------------------------------+-------------+------------------------------+ 21 | | select 1 mysql user | 1.04 ms | 122 us: 8.52x faster (-88%) | 22 | +----------------------------------------------------+-------------+------------------------------+ 23 | | Select <10 cols of 100 chars> from_seq_1_to_100000 | 323 ms | 35.0 ms: 9.22x faster (-89%) | 24 | +----------------------------------------------------+-------------+------------------------------+``` 25 | -------------------------------------------------------------------------------- /testing/benchmarks/benchmark/bulk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | import random 6 | 7 | chars = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "\\Z", "😎", "🌶", "🎤", "🥂" ] 8 | 9 | def randomString(length): 10 | result = ""; 11 | for value in range(length): 12 | result = result + chars[random.randint(0, (len(chars) - 1))] 13 | return result; 14 | 15 | 16 | def bulk(loops, conn, paramstyle): 17 | 18 | # conn.autocommit= False 19 | t0 = pyperf.perf_counter() 20 | s = randomString(100) 21 | vals = [(s,) for i in range(100)] 22 | 23 | range_it = range(loops) 24 | for value in range_it: 25 | cursor = conn.cursor() 26 | if paramstyle == 'qmark': 27 | cursor.executemany("INSERT INTO perfTestTextBatch(t0) VALUES (?)", 28 | vals) 29 | else: 30 | cursor.executemany("INSERT INTO perfTestTextBatch(t0) VALUES (%s)", 31 | vals) 32 | del cursor 33 | 34 | return pyperf.perf_counter() - t0 35 | -------------------------------------------------------------------------------- /testing/benchmarks/benchmark/do_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | 6 | 7 | def do1(loops, conn, paramstyle): 8 | range_it = range(loops) 9 | 10 | t0 = pyperf.perf_counter() 11 | for value in range_it: 12 | cursor = conn.cursor() 13 | cursor.execute('do 1') 14 | del cursor 15 | return pyperf.perf_counter() - t0 16 | -------------------------------------------------------------------------------- /testing/benchmarks/benchmark/do_1000_param.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | 6 | 7 | def do_1000_param(loops, conn, paramstyle): 8 | 9 | range_it = range(loops) 10 | params = [] 11 | for i in range(1, 1001): 12 | params.append(i) 13 | tupleParam = tuple(params) 14 | 15 | t0 = pyperf.perf_counter() 16 | for value in range_it: 17 | cursor = conn.cursor() 18 | if paramstyle == 'qmark': 19 | cursor.execute("DO ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?", tupleParam) 20 | else: 21 | cursor.execute("DO %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", params) 22 | del cursor 23 | return pyperf.perf_counter() - t0 24 | -------------------------------------------------------------------------------- /testing/benchmarks/benchmark/select_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | 6 | 7 | def select_1(loops, conn, paramstyle): 8 | cursor = conn.cursor() 9 | range_it = range(loops) 10 | t0 = pyperf.perf_counter() 11 | for value in range_it: 12 | cursor = conn.cursor() 13 | cursor.execute("select 1") 14 | rows = cursor.fetchall() 15 | del rows 16 | cursor.close() 17 | 18 | return pyperf.perf_counter() - t0 19 | -------------------------------------------------------------------------------- /testing/benchmarks/benchmark/select_1000_rows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | 6 | 7 | def select_1000_rows(loops, conn, paramstyle): 8 | range_it = range(loops) 9 | t0 = pyperf.perf_counter() 10 | for value in range_it: 11 | cursor = conn.cursor() 12 | cursor.execute("select seq, 'abcdefghijabcdefghijabcdefghijaa' from seq_1_to_1000") 13 | rows = cursor.fetchall() 14 | del cursor, rows 15 | return pyperf.perf_counter() - t0 16 | -------------------------------------------------------------------------------- /testing/benchmarks/benchmark/select_100_cols.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | 6 | 7 | def select_100_cols(loops, conn, paramstyle): 8 | range_it = range(loops) 9 | t0 = pyperf.perf_counter() 10 | for value in range_it: 11 | cursor = conn.cursor() 12 | cursor.execute("select * FROM test100") 13 | rows = cursor.fetchall() 14 | del cursor, rows 15 | return pyperf.perf_counter() - t0 16 | 17 | def select_100_cols_execute(loops, conn, paramstyle): 18 | range_it = range(loops) 19 | t0 = pyperf.perf_counter() 20 | for value in range_it: 21 | cursor = conn.cursor(binary=True) 22 | cursor.execute("select * FROM test100 WHERE 1 = ?", (1,)) 23 | rows = cursor.fetchall() 24 | del cursor, rows 25 | return pyperf.perf_counter() - t0 26 | -------------------------------------------------------------------------------- /testing/benchmarks/internal_bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import pyperf 5 | import os 6 | 7 | from benchmarks.benchmark.bulk import bulk 8 | from benchmarks.benchmark.do_1 import do1 9 | from benchmarks.benchmark.select_1 import select_1 10 | from benchmarks.benchmark.do_1000_param import do_1000_param 11 | from benchmarks.benchmark.select_100_cols import select_100_cols, select_100_cols_execute 12 | from benchmarks.benchmark.select_1000_rows import select_1000_rows 13 | 14 | 15 | def run_test(tests, conn, paramstyle): 16 | runner = pyperf.Runner(warmups=1000, processes=1, min_time=10) 17 | for test in tests: 18 | runner.bench_time_func(test['label'], test['method'], conn, paramstyle) 19 | 20 | def test_suite(paramstyle): 21 | ts = [ 22 | {'label': 'BULK Insert', 23 | 'method': bulk}, 24 | {'label': 'DO 1', 25 | 'method': do1}, 26 | {'label': 'DO 1000 params', 27 | 'method': do_1000_param}, 28 | {'label': 'select_100_cols', 29 | 'method': select_100_cols}, 30 | {'label': 'select 1', 'method': select_1}, 31 | {'label': 'select_1000_rows', 'method': select_1000_rows}, 32 | ] 33 | if paramstyle == 'qmark': 34 | ts.append({'label': 'select_100_cols_execute', 'method': select_100_cols_execute}) 35 | return ts 36 | -------------------------------------------------------------------------------- /testing/benchmarks/setup_db.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def init_db(conn, paramstyle): 4 | my_string = "abcdefghi🌟" 5 | str1 = "".join([my_string]*10) 6 | str2 = "".join([my_string]*24) 7 | str3 = "".join([my_string]*1024) 8 | cursor = conn.cursor() 9 | cursor.execute("DROP TABLE IF EXISTS str_test") 10 | cursor.execute("CREATE TABLE str_test (" 11 | "col1 varchar(200), col2 TEXT, col3 TEXT) CHARACTER SET utf8mb4") 12 | vals = [(str1, str2, str3) for i in range(100)] 13 | if paramstyle == 'qmark': 14 | cursor.executemany("INSERT INTO str_test VALUES (?, ?, ?)", vals) 15 | else: 16 | cursor.executemany("INSERT INTO str_test VALUES (%s, %s, %s)", vals) 17 | 18 | del cursor 19 | 20 | cursor = conn.cursor() 21 | cursor.execute("DROP TABLE IF EXISTS num_test") 22 | cursor.execute("CREATE TABLE num_test(" 23 | "col1 smallint, col2 int, col3 smallint, " 24 | "col4 bigint, col5 float, col6 decimal(10,5) )") 25 | vals = [(i % 128, 0xFF+i, 0xFFF+i, 0xFFFF+i, 26 | 10000 + i + 0.3123, 20000 + i + 0.1234) for i in range(1000)] 27 | if paramstyle == 'qmark': 28 | cursor.executemany("INSERT INTO num_test VALUES (?,?,?,?,?,?)", vals) 29 | else: 30 | cursor.executemany("INSERT INTO num_test VALUES (%s,%s,%s,%s,%s,%s)", 31 | vals) 32 | 33 | 34 | cursor.execute("DROP TABLE IF EXISTS test100") 35 | cursor.execute("CREATE TABLE test100 (i1 int,i2 int,i3 int,i4 int,i5 int,i6 int,i7 int,i8 int,i9 int,i10 int,i11 int,i12 int,i13 int,i14 int,i15 int,i16 int,i17 int,i18 int,i19 int,i20 int,i21 int,i22 int,i23 int,i24 int,i25 int,i26 int,i27 int,i28 int,i29 int,i30 int,i31 int,i32 int,i33 int,i34 int,i35 int,i36 int,i37 int,i38 int,i39 int,i40 int,i41 int,i42 int,i43 int,i44 int,i45 int,i46 int,i47 int,i48 int,i49 int,i50 int,i51 int,i52 int,i53 int,i54 int,i55 int,i56 int,i57 int,i58 int,i59 int,i60 int,i61 int,i62 int,i63 int,i64 int,i65 int,i66 int,i67 int,i68 int,i69 int,i70 int,i71 int,i72 int,i73 int,i74 int,i75 int,i76 int,i77 int,i78 int,i79 int,i80 int,i81 int,i82 int,i83 int,i84 int,i85 int,i86 int,i87 int,i88 int,i89 int,i90 int,i91 int,i92 int,i93 int,i94 int,i95 int,i96 int,i97 int,i98 int,i99 int,i100 int)") 36 | cursor.execute("INSERT INTO test100 value (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100)") 37 | 38 | cursor.execute("DROP TABLE IF EXISTS perfTestTextBatch") 39 | try: 40 | cursor.execute("INSTALL SONAME 'ha_blackhole'") 41 | except Error: 42 | pass 43 | createTable = "CREATE TABLE perfTestTextBatch (id MEDIUMINT NOT NULL AUTO_INCREMENT,t0 text, PRIMARY KEY (id)) COLLATE='utf8mb4_unicode_ci'" 44 | try: 45 | cursor.execute(createTable + " ENGINE = BLACKHOLE") 46 | except Exception: 47 | cursor.execute(createTable) 48 | 49 | conn.commit() 50 | del cursor 51 | 52 | 53 | def end_db(conn): 54 | cursor = conn.cursor() 55 | cursor.execute("DROP TABLE IF EXISTS num_test") 56 | del cursor 57 | -------------------------------------------------------------------------------- /testing/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariadb-corporation/mariadb-connector-python/cf7d09ca57241dd66fbc42555bd79237913cd0c1/testing/test/__init__.py -------------------------------------------------------------------------------- /testing/test/base_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | import os 4 | 5 | import mariadb 6 | 7 | from .conf_test import conf 8 | 9 | 10 | def is_skysql(): 11 | if conf()["host"][-13:] == "db.skysql.net": 12 | return True 13 | return False 14 | 15 | 16 | def is_maxscale(): 17 | return (os.environ.get('srv') == "maxscale" or 18 | os.environ.get('srv') == 'skysql-ha') 19 | 20 | 21 | def is_mysql(): 22 | mysql_server = 1 23 | conn = create_connection() 24 | cursor = conn.cursor() 25 | cursor.execute("select version()") 26 | row = cursor.fetchone() 27 | if "MARIADB" in row[0].upper(): 28 | mysql_server = 0 29 | conn.close() 30 | del cursor, conn 31 | return mysql_server 32 | 33 | def get_host_suffix(): 34 | return "@'localhost'" if os.getenv("LOCAL_DB", "container") == "local" else "@'%'" 35 | 36 | def create_connection(additional_conf=None): 37 | default_conf = conf() 38 | if additional_conf is None: 39 | c = {key: value for (key, value) in (default_conf.items())} 40 | else: 41 | c = {key: value for (key, value) in (list(default_conf.items()) + list( 42 | additional_conf.items()))} 43 | return mariadb.connect(**c) 44 | -------------------------------------------------------------------------------- /testing/test/conf_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | 6 | 7 | def glob(): 8 | dm = { 9 | "module": os.environ.get('TEST_MODULE', 'mariadb'), 10 | } 11 | 12 | return dm 13 | 14 | 15 | def conf(): 16 | d = { 17 | "user": os.environ.get('TEST_DB_USER', 'root'), 18 | "host": os.environ.get('TEST_DB_HOST', 'localhost'), 19 | "database": os.environ.get('TEST_DB_DATABASE', 'testp'), 20 | "port": int(os.environ.get('TEST_DB_PORT', '3306')), 21 | } 22 | if os.environ.get('TEST_REQUIRE_TLS'): 23 | if os.environ.get('TEST_REQUIRE_TLS') == "1": 24 | d["ssl"] = True 25 | if os.environ.get('TEST_RESET_SESSION'): 26 | reset = int(os.environ.get('TEST_RESET_SESSION', '1')) 27 | d["pool_reset_connection"] = reset 28 | if os.environ.get('TEST_DB_PASSWORD'): 29 | d["password"] = os.environ.get('TEST_DB_PASSWORD') 30 | return d 31 | -------------------------------------------------------------------------------- /testing/test/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariadb-corporation/mariadb-connector-python/cf7d09ca57241dd66fbc42555bd79237913cd0c1/testing/test/integration/__init__.py -------------------------------------------------------------------------------- /testing/test/integration/test_converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | import unittest 6 | 7 | from mariadb.constants import FIELD_TYPE 8 | from test.base_test import create_connection 9 | 10 | 11 | class foo(int): 12 | def bar(self): pass 13 | 14 | 15 | def timedelta_to_time(s): 16 | return (datetime.datetime.min + s).time() 17 | 18 | 19 | def long_minus(s): 20 | return s - 1 21 | 22 | 23 | def none_to_string(s): 24 | if s is None: 25 | return "None" 26 | return s 27 | 28 | 29 | conversions = { 30 | **{FIELD_TYPE.TIME: timedelta_to_time}, 31 | **{FIELD_TYPE.LONG: long_minus}, 32 | **{FIELD_TYPE.NULL: none_to_string}, 33 | **{FIELD_TYPE.LONGLONG: long_minus}, 34 | } 35 | 36 | 37 | class TestConversion(unittest.TestCase): 38 | 39 | def setUp(self): 40 | self.connection = create_connection({"converter": conversions}) 41 | self.connection.autocommit = False 42 | 43 | def tearDown(self): 44 | del self.connection 45 | 46 | def test_convert_time(self): 47 | cursor = self.connection.cursor() 48 | a = datetime.time(12, 29, 21) 49 | cursor.execute("SELECT cast(? as time)", (a,)) 50 | row = cursor.fetchone() 51 | self.assertEqual(row[0], a) 52 | del cursor 53 | 54 | def test_convert_long(self): 55 | cursor = self.connection.cursor() 56 | a = 12345 57 | cursor.execute("SELECT CAST(? AS SIGNED)", (12345,)) 58 | row = cursor.fetchone() 59 | self.assertEqual(row[0], a - 1) 60 | del cursor 61 | 62 | def test_convert_none(self): 63 | cursor = self.connection.cursor() 64 | cursor.execute("SELECT NULL") 65 | row = cursor.fetchone() 66 | self.assertEqual(row[0], "None") 67 | cursor.execute("SELECT ?", (None,)) 68 | row = cursor.fetchone() 69 | self.assertEqual(row[0], "None") 70 | del cursor 71 | 72 | 73 | if __name__ == '__main__': 74 | unittest.main() 75 | -------------------------------------------------------------------------------- /testing/test/integration/test_cursor_mariadb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | import unittest 6 | 7 | from test.base_test import create_connection 8 | 9 | 10 | class CursorMariaDBTest(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.connection = create_connection() 14 | 15 | def tearDown(self): 16 | del self.connection 17 | 18 | def test_insert_parameter(self): 19 | cursor = self.connection.cursor() 20 | cursor.execute("CREATE TEMPORARY TABLE test_insert_parameter(" 21 | "a int not null auto_increment primary key," 22 | "b int, c int, d varchar(20),e date)") 23 | list_in = [] 24 | for i in range(1, 300001): 25 | row = (i, i, i, "bar", datetime.date(2019, 1, 1)) 26 | list_in.append(row) 27 | cursor.executemany("INSERT INTO test_insert_parameter VALUES " 28 | "(?,?,?,?,?)", list_in) 29 | self.assertEqual(len(list_in), cursor.rowcount) 30 | self.connection.commit() 31 | cursor.execute("SELECT * FROM test_insert_parameter order by a") 32 | list_out = cursor.fetchall() 33 | self.assertEqual(len(list_in), cursor.rowcount) 34 | self.assertEqual(list_in, list_out) 35 | cursor.close() 36 | 37 | def test_update_parameter(self): 38 | cursor = self.connection.cursor() 39 | cursor.execute("CREATE TEMPORARY TABLE test_update_parameter(" 40 | "a int not null auto_increment primary key," 41 | "b int, c int, d varchar(20),e date)") 42 | cursor.execute("set @@autocommit=0") 43 | list_in = [] 44 | for i in range(1, 300001): 45 | row = (i, i, i, "bar", datetime.date(2019, 1, 1)) 46 | list_in.append(row) 47 | cursor.executemany("INSERT INTO test_update_parameter VALUES " 48 | "(?,?,?,?,?)", list_in) 49 | self.assertEqual(len(list_in), cursor.rowcount) 50 | self.connection.commit() 51 | cursor.close() 52 | list_update = [] 53 | 54 | cursor = self.connection.cursor() 55 | cursor.execute("set @@autocommit=0") 56 | for i in range(1, 300001): 57 | row = (i + 1, i) 58 | list_update.append(row) 59 | 60 | cursor.executemany("UPDATE test_update_parameter SET b=? " 61 | "WHERE a=?", list_update) 62 | self.assertEqual(cursor.rowcount, 300000) 63 | self.connection.commit() 64 | cursor.close() 65 | 66 | def test_delete_parameter(self): 67 | cursor = self.connection.cursor() 68 | cursor.execute("CREATE TEMPORARY TABLE test_delete_parameter(" 69 | "a int not null auto_increment primary key," 70 | "b int, c int, d varchar(20),e date)") 71 | cursor.execute("set @@autocommit=0") 72 | list_in = [] 73 | for i in range(1, 300001): 74 | row = (i, i, i, "bar", datetime.date(2019, 1, 1)) 75 | list_in.append(row) 76 | cursor.executemany("INSERT INTO test_delete_parameter VALUES " 77 | "(?,?,?,?,?)", list_in) 78 | self.assertEqual(len(list_in), cursor.rowcount) 79 | self.connection.commit() 80 | cursor.close() 81 | list_delete = [] 82 | 83 | cursor = self.connection.cursor() 84 | cursor.execute("set @@autocommit=0") 85 | for i in range(1, 300001): 86 | list_delete.append((i,)) 87 | 88 | cursor.executemany("DELETE FROM test_delete_parameter WHERE " 89 | "a=?", list_delete) 90 | self.assertEqual(cursor.rowcount, 300000) 91 | self.connection.commit() 92 | cursor.close() 93 | 94 | 95 | if __name__ == '__main__': 96 | unittest.main() 97 | -------------------------------------------------------------------------------- /testing/test/integration/test_cursor_mysql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import datetime 5 | import unittest 6 | 7 | from test.base_test import create_connection, is_maxscale 8 | 9 | 10 | class CursorMySQLTest(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.connection = create_connection() 14 | 15 | def tearDown(self): 16 | del self.connection 17 | 18 | def test_parameter(self): 19 | if is_maxscale(): 20 | self.skipTest("MAXSCALE doesn't support BULK yet") 21 | 22 | cursor = self.connection.cursor() 23 | cursor.execute("CREATE TEMPORARY TABLE test_parameter(" 24 | "a int auto_increment primary key not " 25 | "null, b int, c int, d varchar(20),e date)") 26 | cursor.execute("SET @@autocommit=0") 27 | list_in = [] 28 | for i in range(1, 30000): 29 | row = (i, i, i, "bar", datetime.date(2019, 1, 1)) 30 | list_in.append(row) 31 | cursor.executemany("INSERT INTO test_parameter VALUES " 32 | "(%s,%s,%s,%s,%s)", list_in) 33 | self.connection.commit() 34 | cursor.execute("SELECT * FROM test_parameter order by a") 35 | list_out = cursor.fetchall() 36 | self.assertEqual(list_in, list_out) 37 | 38 | cursor.close() 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /testing/test/integration/test_exception.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from datetime import datetime 6 | import mariadb 7 | import sys, traceback 8 | 9 | from test.base_test import create_connection 10 | 11 | 12 | class TestException(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.connection = create_connection() 16 | 17 | def tearDown(self): 18 | del self.connection 19 | 20 | def test_exception(self): 21 | cursor = self.connection.cursor() 22 | try: 23 | cursor.execute("WRONG QUERY") 24 | except mariadb.ProgrammingError as err: 25 | self.assertEqual(err.sqlstate, "42000") 26 | self.assertEqual(err.errno, 1064) 27 | self.assertTrue(err.errmsg.find("You have an error " 28 | "in your SQL syntax") > -1) 29 | if mariadb._have_asan: 30 | tb = sys.exc_info()[2] 31 | traceback.clear_frames(tb) 32 | pass 33 | 34 | del cursor 35 | 36 | def test_db_unknown_exception(self): 37 | try: 38 | create_connection({"database": "unknown"}) 39 | except mariadb.ProgrammingError as err: 40 | self.assertEqual(err.sqlstate, "42000") 41 | self.assertEqual(err.errno, 1049) 42 | self.assertTrue(err.errmsg.find("Unknown database 'unknown'") > -1) 43 | if mariadb._have_asan: 44 | tb = sys.exc_info()[2] 45 | traceback.clear_frames(tb) 46 | pass 47 | 48 | def test_conn_timeout_exception(self): 49 | start = datetime.today() 50 | try: 51 | create_connection({"connect_timeout": 1, "host": "8.8.8.8"}) 52 | except mariadb.OperationalError as err: 53 | self.assertEqual(err.sqlstate, "HY000") 54 | self.assertEqual(err.errno, 2002) 55 | self.assertTrue(err.errmsg.find("server on '8.8.8.8'") > -1) 56 | end = datetime.today() 57 | difference = end - start 58 | self.assertEqual(difference.days, 0) 59 | self.assertGreaterEqual(difference.total_seconds(), 0.95, 60 | "Connection should have timed out after ~1 second") 61 | if mariadb._have_asan: 62 | tb = sys.exc_info()[2] 63 | traceback.clear_frames(tb) 64 | pass 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /testing/test/integration/test_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | import mariadb 7 | 8 | from test.base_test import create_connection 9 | 10 | 11 | class TestConnection(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.connection = create_connection() 15 | 16 | def tearDown(self): 17 | del self.connection 18 | 19 | def test_conpy_63(self): 20 | version = mariadb.__version__ 21 | version_info = mariadb.__version_info__ 22 | 23 | str_version = list(map(str, version.split('.'))) 24 | 25 | self.assertEqual(int(str_version[0]), version_info[0]) 26 | self.assertEqual(int(str_version[1]), version_info[1]) 27 | 28 | # patch might contain letters 29 | try: 30 | self.assertEqual(int(str_version[2]), version_info[2]) 31 | except Exception: 32 | self.assertEqual(str_version[2], version_info[2]) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /testing/test/integration/test_nondbapi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | 3 | # -*- coding: utf-8 -*- 4 | 5 | import unittest 6 | import mariadb 7 | 8 | from test.base_test import create_connection, is_skysql, is_maxscale, is_mysql, get_host_suffix 9 | from test.conf_test import conf 10 | 11 | 12 | class CursorTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.connection = create_connection() 16 | 17 | def tearDown(self): 18 | del self.connection 19 | 20 | def test_ping(self): 21 | if is_maxscale(): 22 | self.skipTest("MAXSCALE return wrong thread id") 23 | 24 | new_conn = create_connection() 25 | id = new_conn.connection_id 26 | self.connection.kill(id) 27 | try: 28 | new_conn.ping() 29 | except mariadb.InterfaceError: 30 | pass 31 | del new_conn 32 | new_conn = create_connection() 33 | new_conn.auto_reconnect = True 34 | id = new_conn.connection_id 35 | self.connection.kill(id) 36 | new_conn.ping() 37 | new_id = new_conn.connection_id 38 | self.assertTrue(id != new_id) 39 | del new_conn 40 | 41 | def test_change_user(self): 42 | if is_skysql(): 43 | self.skipTest("SkySQL failure") 44 | if is_maxscale(): 45 | self.skipTest("MAXSCALE doesn't get new user immediately") 46 | if self.connection.server_name == "localhost": 47 | curs = self.connection.cursor(buffered=True) 48 | curs.execute("select * from information_schema.plugins " 49 | "where plugin_name='unix_socket' " 50 | "and plugin_status='ACTIVE'") 51 | if curs.rowcount > 0: 52 | del curs 53 | self.skipTest("unix_socket is active") 54 | del curs 55 | 56 | default_conf = conf() 57 | cursor = self.connection.cursor() 58 | cursor.execute("drop user if exists foo") 59 | if is_mysql() and self.connection.server_version < 80000: 60 | cursor.execute("create user foo"+get_host_suffix()) 61 | cursor.execute("GRANT ALL on `" 62 | + default_conf["database"] + 63 | "`.* TO foo"+get_host_suffix()+" IDENTIFIED BY " 64 | "'heyPassw-!µ20§rd'") 65 | else: 66 | cursor.execute("create user foo"+get_host_suffix()+" IDENTIFIED " 67 | "BY 'heyPassw-!µ20§rd'") 68 | cursor.execute("GRANT ALL on `" + default_conf["database"] + 69 | "`.* TO foo"+get_host_suffix()) 70 | new_conn = create_connection() 71 | new_conn.change_user("foo", "heyPassw-!µ20§rd", "") 72 | self.assertEqual("foo", new_conn.user) 73 | cursor.execute("drop user foo"+get_host_suffix()) 74 | del new_conn 75 | del cursor 76 | 77 | def test_reconnect(self): 78 | if is_maxscale(): 79 | self.skipTest("MAXSCALE wrong thread id") 80 | new_conn = create_connection() 81 | conn1_id = new_conn.connection_id 82 | self.connection.kill(conn1_id) 83 | new_conn.reconnect() 84 | conn2_id = new_conn.connection_id 85 | self.assertFalse(conn1_id == conn2_id) 86 | del new_conn 87 | 88 | def test_reset(self): 89 | if self.connection.server_version < 100204: 90 | self.skipTest("RESET not supported") 91 | 92 | cursor = self.connection.cursor() 93 | cursor.execute("SELECT 1 UNION SELECT 2") 94 | try: 95 | self.connection.ping() 96 | except mariadb.InterfaceError: 97 | pass 98 | 99 | self.connection.reset() 100 | self.connection.ping() 101 | del cursor 102 | 103 | def test_warnings(self): 104 | conn = self.connection 105 | cursor = conn.cursor() 106 | 107 | cursor.execute("SET session sql_mode=''") 108 | cursor.execute("CREATE TEMPORARY TABLE test_warnings (a tinyint)") 109 | cursor.execute("INSERT INTO test_warnings VALUES (300)") 110 | 111 | self.assertEqual(conn.warnings, 1) 112 | self.assertEqual(conn.warnings, cursor.warnings) 113 | del cursor 114 | 115 | def test_server_infos(self): 116 | self.assertTrue(self.connection.server_info) 117 | self.assertTrue(self.connection.server_version > 0) 118 | 119 | def test_escape(self): 120 | cursor = self.connection.cursor() 121 | cursor.execute("CREATE TEMPORARY TABLE test_escape (a varchar(100))") 122 | str = 'This is a \ and a \"' # noqa: W605 123 | cmd = "INSERT INTO test_escape VALUES('%s')" % str 124 | 125 | try: 126 | cursor.execute(cmd) 127 | except mariadb.DatabaseError: 128 | pass 129 | 130 | str = self.connection.escape_string(str) 131 | cmd = "INSERT INTO test_escape VALUES('%s')" % str 132 | cursor.execute(cmd) 133 | del cursor 134 | 135 | def test_conpy279(self): 136 | conn = self.connection 137 | default_conf = conf() 138 | if "password" not in default_conf: 139 | default_conf["password"] = None 140 | try: 141 | conn.change_user(None, None, None) 142 | except TypeError: 143 | pass 144 | conn.change_user(default_conf["user"], default_conf["password"], None) 145 | conn.close() 146 | 147 | 148 | if __name__ == '__main__': 149 | unittest.main() 150 | -------------------------------------------------------------------------------- /testing/test/integration/test_pooling.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | import mariadb 7 | import platform 8 | 9 | from test.base_test import create_connection, conf, is_skysql, is_maxscale 10 | 11 | 12 | @unittest.skipIf(platform.python_implementation() == "PyPy", 13 | "skip pooling tests for PyPy") 14 | class TestPooling(unittest.TestCase): 15 | 16 | def setUp(self): 17 | pass 18 | 19 | # self.connection = create_connection() 20 | # self.connection.autocommit = False 21 | 22 | def tearDown(self): 23 | pass 24 | 25 | # del self.connection 26 | 27 | def test_connection_pools(self): 28 | pool = mariadb.ConnectionPool(pool_name="test_connection") 29 | self.assertEqual(mariadb._CONNECTION_POOLS["test_connection"], pool) 30 | pool.close() 31 | self.assertEqual(mariadb._CONNECTION_POOLS, {}) 32 | 33 | def test_conpy39(self): 34 | try: 35 | mariadb.ConnectionPool() 36 | except mariadb.ProgrammingError: 37 | pass 38 | 39 | def test_conpy246(self): 40 | # test if a pooled connection will be roll backed 41 | 42 | default_conf = conf() 43 | 44 | pool = mariadb.ConnectionPool(pool_name="CONPY246", 45 | pool_size=1, 46 | pool_reset_connection=False, 47 | **default_conf) 48 | conn = pool.get_connection() 49 | cursor = conn.cursor() 50 | cursor.execute("DROP TABLE IF EXISTS conpy246") 51 | cursor.execute("CREATE TABLE conpy246(a int)") 52 | cursor.execute("INSERT INTO conpy246 VALUES (1)") 53 | cursor.close() 54 | conn.close() 55 | conn = pool.get_connection() 56 | cursor = conn.cursor() 57 | cursor.execute("SELECT * FROM conpy246") 58 | self.assertEqual(cursor.rowcount, 0) 59 | cursor.execute("DROP TABLE conpy246") 60 | cursor.close() 61 | conn.close() 62 | pool.close() 63 | 64 | def test_conpy250(self): 65 | default_conf = conf() 66 | pool = mariadb.ConnectionPool(pool_name="CONPY250", 67 | pool_size=16, 68 | pool_reset_connection=False, 69 | pool_validation_interval=0, 70 | **default_conf) 71 | self.assertEqual(pool.connection_count, 16) 72 | pool.close() 73 | self.assertEqual(pool.connection_count, 0) 74 | 75 | def test_conpy247_1(self): 76 | default_conf = conf() 77 | pool = mariadb.ConnectionPool(pool_name="CONPY247_1", 78 | pool_size=1, 79 | pool_reset_connection=False, 80 | pool_validation_interval=0, 81 | **default_conf) 82 | 83 | # service connection 84 | conn = create_connection() 85 | cursor = conn.cursor() 86 | 87 | pconn = pool.get_connection() 88 | old_id = pconn.connection_id 89 | cursor.execute("KILL %s" % (old_id,)) 90 | cursor.close() 91 | pconn.close() 92 | 93 | pconn = pool.get_connection() 94 | self.assertNotEqual(old_id, pconn.connection_id) 95 | 96 | conn.close() 97 | pool.close() 98 | 99 | def test_conpy247_2(self): 100 | default_conf = conf() 101 | pool = mariadb.ConnectionPool(pool_name="CONPY247_2", 102 | pool_size=1, 103 | pool_reset_connection=True, 104 | pool_validation_interval=0, 105 | **default_conf) 106 | 107 | # service connection 108 | conn = create_connection() 109 | cursor = conn.cursor() 110 | 111 | pconn = pool.get_connection() 112 | old_id = pconn.connection_id 113 | cursor.execute("KILL %s" % (old_id,)) 114 | cursor.close() 115 | pconn.close() 116 | 117 | pconn = pool.get_connection() 118 | self.assertNotEqual(old_id, pconn.connection_id) 119 | 120 | conn.close() 121 | pool.close() 122 | 123 | def test_conpy247_3(self): 124 | default_conf = conf() 125 | pool = mariadb.ConnectionPool(pool_name="CONPY247_3", 126 | pool_size=10, 127 | pool_reset_connection=True, 128 | pool_validation_interval=0, 129 | **default_conf) 130 | 131 | # service connection 132 | conn = create_connection() 133 | cursor = conn.cursor() 134 | ids = [] 135 | cursor.execute("DROP PROCEDURE IF EXISTS p1") 136 | sql = """CREATE PROCEDURE p1() 137 | BEGIN 138 | SELECT 1; 139 | SELECT 2; 140 | END""" 141 | 142 | cursor.execute(sql) 143 | 144 | for i in range(0, 10): 145 | pconn = pool.get_connection() 146 | ids.append(pconn.connection_id) 147 | cursor.execute("KILL %s" % (pconn.connection_id,)) 148 | pconn.close() 149 | 150 | new_ids = [] 151 | 152 | for i in range(0, 10): 153 | pconn = pool.get_connection() 154 | new_ids.append(pconn.connection_id) 155 | self.assertEqual(pconn.connection_id in ids, False) 156 | cursor = pconn.cursor() 157 | cursor.callproc("p1") 158 | cursor.close() 159 | pconn.close() 160 | 161 | for i in range(0, 10): 162 | pconn = pool.get_connection() 163 | self.assertEqual(pconn.connection_id in new_ids, True) 164 | pconn.close() 165 | 166 | conn.close() 167 | pool.close() 168 | 169 | def test_conpy245(self): 170 | # we can't test performance here, but we can check if LRU works. 171 | # All connections must have been used the same number of times. 172 | 173 | default_conf = conf() 174 | pool_size = 64 175 | iterations = 100 176 | 177 | pool = mariadb.ConnectionPool(pool_name="CONPY245", 178 | pool_size=pool_size, 179 | **default_conf) 180 | for i in range(0, iterations): 181 | for j in range(0, pool_size): 182 | conn = pool.get_connection() 183 | conn.close() 184 | 185 | for i in range(0, pool_size): 186 | conn = pool.get_connection() 187 | self.assertEqual(conn._used, iterations + 1) 188 | conn.close() 189 | 190 | pool.close() 191 | 192 | def test_connection_pool_conf(self): 193 | pool = mariadb.ConnectionPool(pool_name="test_conf") 194 | default_conf = conf() 195 | conn = create_connection() 196 | try: 197 | pool.add_connection(conn) 198 | except mariadb.PoolError: 199 | pass 200 | try: 201 | pool.set_config(**default_conf) 202 | except mariadb.Error: 203 | pool.close() 204 | raise 205 | 206 | pool.add_connection(conn) 207 | c = pool.get_connection() 208 | self.assertEqual(c, conn) 209 | pool.close() 210 | 211 | def test_connection_pool_maxconn(self): 212 | default_conf = conf() 213 | pool = mariadb.ConnectionPool(pool_name="test_max_size", pool_size=6, 214 | **default_conf) 215 | connections = [] 216 | for i in range(0, 6): 217 | connections.append(pool.get_connection()) 218 | self.assertRaises(mariadb.PoolError, lambda:pool.get_connection()) 219 | 220 | for c in connections: 221 | c.close() 222 | pool.close() 223 | 224 | def test_connection_pool_add(self): 225 | default_conf = conf() 226 | pool = mariadb.ConnectionPool(pool_name="test_connection_pool_add") 227 | try: 228 | pool.set_config(**default_conf) 229 | except mariadb.Error: 230 | pool.close() 231 | raise 232 | 233 | for i in range(1, 6): 234 | pool.add_connection() 235 | try: 236 | pool.add_connection() 237 | except mariadb.PoolError: 238 | pass 239 | pool.close() 240 | 241 | def test_conpy69(self): 242 | if is_skysql(): 243 | self.skipTest("skipping on SkySQL") 244 | if is_maxscale(): 245 | self.skipTest("skipping on maxscale, bug") 246 | 247 | conn = create_connection() 248 | conn.autocommit = True 249 | cursor1 = conn.cursor() 250 | cursor1.execute("CREATE SCHEMA IF NOT EXISTS 中文考试") 251 | cursor1.execute("COMMIT") 252 | default_conf = conf() 253 | default_conf["database"] = "中文考试" 254 | pool = mariadb.ConnectionPool(pool_name="test_conpy69") 255 | try: 256 | pool.set_config(**default_conf) 257 | except mariadb.Error: 258 | pool.close() 259 | raise 260 | 261 | try: 262 | for i in range(1, 6): 263 | pool.add_connection() 264 | conn = mariadb.connect(pool_name="test_conpy69") 265 | conn.autocommit = True 266 | cursor = conn.cursor() 267 | cursor.execute("select database()") 268 | row = cursor.fetchone() 269 | self.assertEqual(row[0], "中文考试") 270 | cursor.execute("CREATE TEMPORARY TABLE t1 " 271 | "(a varchar(255)) character set utf8mb4") 272 | cursor.execute("insert into t1 values (?)", ("123.45 中文考试",)) 273 | cursor.execute("select a from t1", buffered=True) 274 | row = cursor.fetchone() 275 | self.assertEqual(row[0], "123.45 中文考试") 276 | cursor1.execute("DROP SCHEMA 中文考试") 277 | finally: 278 | pool.close() 279 | 280 | def test__CONNECTION_POOLS(self): 281 | default_conf = conf() 282 | pool = mariadb.ConnectionPool(pool_name="test_use", **default_conf) 283 | conn = mariadb.connect(pool_name="test_use") 284 | cursor = conn.cursor() 285 | cursor.execute("SELECT 1") 286 | row = cursor.fetchone() 287 | self.assertEqual(row[0], 1) 288 | del cursor 289 | pool.close() 290 | 291 | def test_create_pool_from_conn(self): 292 | default_conf = conf() 293 | key = "t1" 294 | conn = mariadb.connect(pool_name=key, **default_conf) 295 | cursor = conn.cursor() 296 | del mariadb._CONNECTION_POOLS["t1"] 297 | self.assertEqual(mariadb._CONNECTION_POOLS, {}) 298 | try: 299 | cursor.execute("SELECT 1") 300 | except mariadb.ProgrammingError: 301 | pass 302 | 303 | def test_pool_getter(self): 304 | default_conf = conf() 305 | mariadb.connect(pool_name="getter_test", 306 | pool_size=4, **default_conf) 307 | p = mariadb._CONNECTION_POOLS["getter_test"] 308 | self.assertEqual(p.pool_name, "getter_test") 309 | self.assertEqual(p.pool_size, 4) 310 | if "pool_reset_connection" in default_conf: 311 | self.assertEqual(p.pool_reset_connection, 312 | default_conf["pool_reset_connection"]) 313 | else: 314 | self.assertEqual(p.pool_reset_connection, True) 315 | self.assertEqual(p.max_size, 64) 316 | mariadb._CONNECTION_POOLS["getter_test"].close() 317 | 318 | def test_pool_connection_reset(self): 319 | default_conf = conf() 320 | conn = mariadb.connect(pool_name="reset_test", 321 | pool_size=1, **default_conf) 322 | cursor = conn.cursor() 323 | cursor.execute("SELECT 1") 324 | cursor.close() 325 | conn.close() 326 | conn = mariadb.connect(pool_name="reset_test") 327 | cursor = conn.cursor() 328 | cursor.execute("SELECT 2") 329 | row = cursor.fetchone() 330 | self.assertEqual(row[0], 2) 331 | mariadb._CONNECTION_POOLS["reset_test"].close() 332 | 333 | def test_conpy40(self): 334 | default_conf = conf() 335 | pool = mariadb.ConnectionPool(pool_name='test_conpy40') 336 | 337 | try: 338 | pool.set_config(pool_size=3) 339 | except mariadb.PoolError: 340 | pass 341 | 342 | try: 343 | pool.set_config(**default_conf) 344 | except mariadb.Error: 345 | pool.close() 346 | raise 347 | 348 | for j in range(3): 349 | c = mariadb.connect(**default_conf) 350 | pool.add_connection(c) 351 | pool.close() 352 | 353 | def test_pool_add(self): 354 | pool = mariadb.ConnectionPool(pool_name="test_pool_add") 355 | try: 356 | mariadb.ConnectionPool(pool_name="test_pool_add") 357 | except mariadb.ProgrammingError: 358 | pass 359 | pool.close() 360 | self.assertEqual(mariadb._CONNECTION_POOLS, {}) 361 | 362 | def test_conpy256(self): 363 | size = 10 364 | connections = [] 365 | default_conf = conf() 366 | pool = mariadb.ConnectionPool(pool_name="test_conpy256", 367 | pool_size=size, **default_conf) 368 | for i in range(size): 369 | c= pool.get_connection() 370 | self.assertNotEqual(c in connections, True) 371 | connections.append(c) 372 | 373 | pool.close() 374 | 375 | if __name__ == '__main__': 376 | unittest.main() 377 | -------------------------------------------------------------------------------- /testing/test/integration/test_xa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python -O 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import mariadb 6 | 7 | from test.base_test import create_connection 8 | 9 | 10 | class TestCA(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.connection = create_connection() 14 | self.connection.autocommit = False 15 | 16 | def tearDown(self): 17 | del self.connection 18 | 19 | def test_xid(self): 20 | con = create_connection() 21 | xid = con.xid(1, "foo", "bar") 22 | self.assertEqual(xid, (1, "foo", "bar")) 23 | 24 | # default for format_id is 1 25 | xid = con.xid(0, "foo", "bar") 26 | self.assertEqual(xid, (1, "foo", "bar")) 27 | 28 | # parameter too long: 29 | try: 30 | xid = con.xid(0, "a" * 65, "bar") 31 | except mariadb.ProgrammingError: 32 | pass 33 | try: 34 | xid = con.xid(0, "foo", "b" * 65) 35 | except mariadb.ProgrammingError: 36 | pass 37 | 38 | def test_tpc_begin(self): 39 | con = create_connection() 40 | xid = con.xid(0, "1234567890", "2345") 41 | try: 42 | con.tpc_begin(xid) 43 | except mariadb.NotSupportedError: 44 | pass 45 | 46 | def test_tpc_commit(self): 47 | con = create_connection() 48 | xid = con.xid(0, "1234567891", "2345") 49 | cursor = con.cursor() 50 | cursor.execute("DROP TABLE IF EXISTS t1") 51 | cursor.execute("CREATE TABLE t1 (a int)") 52 | try: 53 | con.tpc_begin(xid) 54 | cursor.execute("INSERT INTO t1 VALUES (1),(2)") 55 | cursor.close() 56 | con.tpc_commit() 57 | finally: 58 | con.close() 59 | 60 | def test_tpc_rollback_without_prepare(self): 61 | con = create_connection() 62 | try: 63 | xid = con.xid(0, "1234567892", "2345") 64 | con.tpc_begin(xid) 65 | cursor = con.cursor() 66 | cursor.execute("SELECT 1") 67 | cursor.close() 68 | con.tpc_rollback() 69 | finally: 70 | con.close() 71 | 72 | def test_tpc_commit_with_prepare(self): 73 | con = create_connection() 74 | try: 75 | xid = con.xid(0, "1234567893", "2345") 76 | con.tpc_begin(xid) 77 | cursor = con.cursor() 78 | cursor.execute("SELECT 1") 79 | cursor.close() 80 | con.tpc_prepare() 81 | con.tpc_commit() 82 | finally: 83 | con.close() 84 | 85 | def test_tpc_rollback_with_prepare(self): 86 | con = create_connection() 87 | try: 88 | xid = con.xid(0, "1234567894", "2345") 89 | con.tpc_begin(xid) 90 | cursor = con.cursor() 91 | cursor.execute("SELECT 1") 92 | cursor.close() 93 | con.tpc_prepare() 94 | con.tpc_rollback() 95 | finally: 96 | con.close() 97 | 98 | def test_tpc_begin_in_transaction_fails(self): 99 | con = create_connection() 100 | try: 101 | xid = con.xid(0, "1234567895", "2345") 102 | 103 | cursor = con.cursor() 104 | cursor.execute("BEGIN") 105 | cursor.execute("SELECT 1") 106 | cursor.close() 107 | self.assertRaises(mariadb.IntegrityError, 108 | con.tpc_begin, xid) 109 | finally: 110 | con.close() 111 | 112 | def test_commit_in_tpc_fails(self): 113 | con = create_connection() 114 | try: 115 | xid = con.xid(0, "1234567897", "2345") 116 | con.tpc_begin(xid) 117 | 118 | self.assertRaises(mariadb.ProgrammingError, con.commit) 119 | finally: 120 | con.close() 121 | 122 | def test_rollback_in_tpc_fails(self): 123 | # calling rollback() within a TPC transaction fails with 124 | # ProgrammingError. 125 | con = create_connection() 126 | try: 127 | xid = con.xid(0, "1234567898", "2345") 128 | con.tpc_begin(xid) 129 | 130 | self.assertRaises(mariadb.ProgrammingError, con.rollback) 131 | finally: 132 | con.close() 133 | 134 | 135 | if __name__ == '__main__': 136 | unittest.main() 137 | --------------------------------------------------------------------------------