├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ ├── init │ └── start.sql │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── .travis.yml ├── CHANGELOG.md ├── 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 │ └── 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', 'sqlalchemy'] 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: mariadb-corporation/connector-ci-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": "3.13"}, {"name": "MariaDB 11.4", "os": "ubuntu-latest", "db-type": "community", "db-tag": "11.4", "python": "3.14"}, {"name": "MariaDB 11.4", "os": "windows-latest", "db-type": "community", "db-tag": "11.4", "python": "3.14"}, {"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: mariadb-corporation/connector-ci-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 | init-script-folder: ${{ format('{0}/.github/workflows/init', github.workspace) }} 70 | 71 | - uses: actions/setup-python@v5 72 | id: setup-python 73 | with: 74 | python-version: ${{ matrix.python || '3.13' }} 75 | allow-prereleases: true 76 | 77 | - name: Clone C/C 78 | uses: GuillaumeFalourd/clone-github-repo-action@v2.3 79 | with: 80 | branch: '3.4' 81 | owner: 'mariadb-corporation' 82 | repository: 'mariadb-connector-c' 83 | 84 | - name: Clone SQLAlchemy 85 | if: ${{ startsWith(matrix.python, '3.13') }} 86 | uses: GuillaumeFalourd/clone-github-repo-action@v2.3 87 | with: 88 | branch: main 89 | owner: 'sqlalchemy' 90 | repository: 'sqlalchemy' 91 | 92 | - name: c/c make ubuntu 93 | if: ${{ startsWith(matrix.os, 'ubuntu') }} 94 | run: | 95 | cd ${{ github.workspace }}/mariadb-connector-c 96 | cmake . -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB=On -DCMAKE_INSTALL_PREFIX=/usr 97 | make -j4 98 | sudo make install 99 | echo "MARIADB_PLUGIN_DIR=`mariadb_config --plugindir`" >> $GITHUB_ENV 100 | echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb" >> $GITHUB_ENV 101 | 102 | - name: c/c make macos 103 | if: ${{ startsWith(matrix.os, 'mac') }} 104 | run: | 105 | cd ${{ github.workspace }}/mariadb-connector-c 106 | cmake . -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB=On 107 | make -j4 108 | sudo make install 109 | ls -lrt /usr/local/lib/mariadb/plugin 110 | echo "MARIADB_PLUGIN_DIR=`mariadb_config --plugindir`" >> $GITHUB_ENV 111 | echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb" >> $GITHUB_ENV 112 | echo "DYLD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/mariadb:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV 113 | echo "DYLD_FALLBACK_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/mariadb" >> $GITHUB_ENV 114 | 115 | - name: c/c make windows 116 | if: ${{ startsWith(matrix.os, 'windows') }} 117 | shell: powershell 118 | run: | 119 | cd ${{ github.workspace }}/mariadb-connector-c 120 | $MARIADB_CC_INSTALL_DIR = "$env:USERPROFILE/conc" 121 | cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_CURL=ON -DCMAKE_INSTALL_PREFIX="$MARIADB_CC_INSTALL_DIR" 122 | cmake --build . --config RelWithDebInfo 123 | cmake --install . --config RelWithDebInfo 124 | echo "MARIADB_CC_INSTALL_DIR=$MARIADB_CC_INSTALL_DIR" >> $env:GITHUB_ENV 125 | echo "MARIADB_PLUGIN_DIR=$MARIADB_CC_INSTALL_DIR/lib/mariadb/plugin" >> $env:GITHUB_ENV 126 | 127 | - name: Run SQLAlchemy tests 128 | if: ${{ startsWith(matrix.python, '3.13') }} 129 | shell: bash 130 | run: | 131 | python --version 132 | python -m pip install . 133 | python -m pip install pytest typing_extensions 134 | cd ${{ github.workspace }}/sqlalchemy 135 | python -m pytest --dburi=mysql+mariadbconnector://$TEST_DB_USER:$TEST_DB_PASSWORD@$TEST_DB_HOST:$TEST_DB_PORT/$TEST_DB --backend-only 136 | env: 137 | TEST_DB_USER: ${{ env.MYSQL_TEST_USER }} 138 | TEST_DB_HOST: ${{ env.MYSQL_TEST_HOST }} 139 | TEST_DB_DATABASE: ${{ env.MYSQL_TEST_DB }} 140 | TEST_DB_PORT: ${{ env.MYSQL_TEST_PORT }} 141 | TEST_DB_PASSWORD: ${{ env.MYSQL_TEST_PASSWD }} 142 | TEST_DB: ${{ env.MYSQL_TEST_DB }} 143 | 144 | - name: Run test suite 145 | shell: bash 146 | run: | 147 | python --version 148 | python -m pip install . 149 | cd testing 150 | python -m unittest discover -v 151 | env: 152 | TEST_DB_USER: ${{ env.MYSQL_TEST_USER }} 153 | TEST_DB_HOST: ${{ env.MYSQL_TEST_HOST }} 154 | TEST_DB_DATABASE: ${{ env.MYSQL_TEST_DB }} 155 | TEST_DB_PORT: ${{ env.MYSQL_TEST_PORT }} 156 | TEST_DB_PASSWORD: ${{ env.MYSQL_TEST_PASSWD }} 157 | LOCAL_DB: ${{ steps.setup-env.outputs.database-type }} 158 | PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} 159 | -------------------------------------------------------------------------------- /.github/workflows/init/start.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS test_schema 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Generate and Update API Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | branches: [1.1] 6 | release: 7 | types: [published] 8 | branches: [1.1] 9 | 10 | jobs: 11 | update-docs: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Python project 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: '3.12' 24 | 25 | 26 | - name: Clone C/C 27 | uses: GuillaumeFalourd/clone-github-repo-action@v2.3 28 | with: 29 | branch: '3.4' 30 | owner: 'mariadb-corporation' 31 | repository: 'mariadb-connector-c' 32 | 33 | - name: c/c make ubuntu 34 | run: | 35 | cd ${{ github.workspace }}/mariadb-connector-c 36 | cmake . -DCMAKE_BUILD_TYPE=Release -DWITH_EXTERNAL_ZLIB=On -DCMAKE_INSTALL_PREFIX=/usr 37 | make -j4 38 | sudo make install 39 | echo "MARIADB_PLUGIN_DIR=`mariadb_config --plugindir`" >> $GITHUB_ENV 40 | echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/mariadb" >> $GITHUB_ENV 41 | 42 | 43 | - name: Install dependencies 44 | run: | 45 | # Install your project dependencies 46 | pip install -r docs/requirements.txt 47 | pip install -e . 48 | 49 | - name: Generate Sphinx documentation 50 | run: | 51 | sphinx-build -b markdown docs/source docs/_build/markdown 52 | 53 | - name: Ensure fork exists and is up to date 54 | uses: actions/github-script@v7 55 | with: 56 | github-token: ${{ secrets.SPHINX_TOKEN }} 57 | script: | 58 | const owner = 'rusher'; 59 | const repo = 'mariadb-docs'; 60 | const upstream = 'mariadb-corporation'; 61 | const upstreamRepo = 'mariadb-docs'; 62 | // Check if fork exists 63 | let forkExists = false; 64 | try { 65 | await github.rest.repos.get({ owner, repo }); 66 | forkExists = true; 67 | } catch (e) { 68 | forkExists = false; 69 | } 70 | // Create fork if not exists 71 | if (!forkExists) { 72 | await github.rest.repos.createFork({ owner: upstream, repo: upstreamRepo }); 73 | // Wait for fork to be ready 74 | let ready = false; 75 | for (let i = 0; i < 10; i++) { 76 | try { 77 | await github.rest.repos.get({ owner, repo }); 78 | ready = true; 79 | break; 80 | } catch (e) { 81 | await new Promise(res => setTimeout(res, 5000)); 82 | } 83 | } 84 | if (!ready) throw new Error('Fork not ready after waiting.'); 85 | } 86 | 87 | - name: Checkout documentation repository (fork) 88 | uses: actions/checkout@v4 89 | with: 90 | repository: rusher/mariadb-docs 91 | token: ${{ secrets.SPHINX_TOKEN }} 92 | path: mariadb-docs 93 | ref: main 94 | 95 | - name: Add upstream and fetch latest main 96 | run: | 97 | cd mariadb-docs 98 | git remote add upstream https://github.com/mariadb-corporation/mariadb-docs.git || true 99 | git fetch upstream 100 | git checkout main 101 | git pull upstream main 102 | 103 | - name: Update documentation subdirectory 104 | run: | 105 | # Remove existing documentation in target subdirectory 106 | 107 | rm -f mariadb-docs/connectors/mariadb-connector-python/api.md 108 | rm -f mariadb-docs/connectors/mariadb-connector-python/bugs.md 109 | rm -f mariadb-docs/connectors/mariadb-connector-python/connection.md 110 | rm -f mariadb-docs/connectors/mariadb-connector-python/constants.md 111 | rm -f mariadb-docs/connectors/mariadb-connector-python/cursor.md 112 | rm -f mariadb-docs/connectors/mariadb-connector-python/faq.md 113 | rm -f mariadb-docs/connectors/mariadb-connector-python/install.md 114 | rm -f mariadb-docs/connectors/mariadb-connector-python/license.md 115 | rm -f mariadb-docs/connectors/mariadb-connector-python/module.md 116 | rm -f mariadb-docs/connectors/mariadb-connector-python/pool.md 117 | rm -f mariadb-docs/connectors/mariadb-connector-python/pooling.md 118 | rm -f mariadb-docs/connectors/mariadb-connector-python/index.md 119 | rm -f mariadb-docs/connectors/mariadb-connector-python/usage.md 120 | rm -f mariadb-docs/connectors/mariadb-connector-python/release.md 121 | 122 | # Copy new documentation 123 | cp -r docs/_build/markdown/* mariadb-docs/connectors/mariadb-connector-python/ 124 | mv -f mariadb-docs/connectors/mariadb-connector-python/index.md mariadb-docs/connectors/mariadb-connector-python/README.md 125 | 126 | # Optional: Add any additional processing here 127 | # e.g., update index files, fix relative links, etc. 128 | 129 | - name: Commit and push changes to fork 130 | run: | 131 | cd mariadb-docs 132 | git config user.name "github-actions[bot]" 133 | git config user.email "github-actions[bot]@users.noreply.github.com" 134 | git checkout -b auto-docs-update-${{ github.run_number }} 135 | git add connectors/mariadb-connector-python/ 136 | git commit -m "Update API documentation from ${{ github.repository }}" 137 | git push https://x-access-token:${{ secrets.SPHINX_TOKEN }}@github.com/rusher/mariadb-docs.git auto-docs-update-${{ github.run_number }} 138 | 139 | - name: Create Pull Request to Upstream 140 | uses: actions/github-script@v7 141 | with: 142 | github-token: ${{ secrets.SPHINX_TOKEN }} 143 | script: | 144 | const branch = `auto-docs-update-${{ github.run_number }}`; 145 | const prTitle = "Auto-update: API Documentation from ${{ github.repository }}"; 146 | 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 }}`; 147 | try { 148 | // Check if a PR already exists for this branch 149 | const { data: pulls } = await github.rest.pulls.list({ 150 | owner: 'mariadb-corporation', 151 | repo: 'mariadb-docs', 152 | head: `rusher:${branch}`, 153 | base: 'main', 154 | state: 'open', 155 | }); 156 | if (pulls.length === 0) { 157 | const pr = await github.rest.pulls.create({ 158 | owner: 'mariadb-corporation', 159 | repo: 'mariadb-docs', 160 | title: prTitle, 161 | head: `rusher:${branch}`, 162 | base: 'main', 163 | body: prBody, 164 | maintainer_can_modify: false 165 | }); 166 | console.log('PR created: ', pr.data.html_url); 167 | } else { 168 | console.log('PR already exists: ', pulls[0].html_url); 169 | } 170 | } catch (error) { 171 | core.setFailed(`Failed to create PR: ${error.message}`); 172 | } 173 | -------------------------------------------------------------------------------- /.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 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 be reported already by someone else or it was already fixed in a more recent version. 13 | 14 | What? 15 | ^^^^^ 16 | We need to know what you did, what happened and what you wanted to happen. A report stating that method xyz() hangs, will not allow us to provide you with an advice or fix, since we just don't know what the method is doing. 17 | Beside versions, a good bug report contains a short script which reproduces the problem. Sometimes it is also necessary to 18 | provide the definition (and data) of used tables. 19 | 20 | Versions of components 21 | ^^^^^^^^^^^^^^^^^^^^^^ 22 | MariaDB Connector/Python interacts with two other components: The database server and MariaDB Connector/C. The latter one is responsible for client/server communication. 23 | 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. 24 | 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 the time to figure out the real problem. So try to keep it simple and focus on the real problem. 34 | 35 | The sane applies for database related components like tables, views, and stored procedures. Avoid table definitions with hundreds of columns if the problem can be reproduced with only 4 columns. 36 | 37 | Only report one problem in one bug report 38 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 39 | If you have encountered two or more bugs which are not related, please file an issue for each of them. 40 | 41 | Crashes 42 | ^^^^^^^ 43 | If your application crashes, please also provide if possible a backtrace and output of the exception. 44 | 45 | Report bugs in English only! 46 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 47 | 48 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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.xid 14 | 15 | *Since version 1.0.1.* 16 | 17 | ------------------ 18 | Connection methods 19 | ------------------ 20 | 21 | .. automethod:: mariadb.connections.Connection.begin 22 | 23 | *Since version 1.1.0.* 24 | 25 | .. automethod:: mariadb.connections.Connection.commit 26 | 27 | .. automethod:: mariadb.connections.Connection.change_user 28 | 29 | .. automethod:: mariadb.connections.Connection.close 30 | 31 | .. automethod:: mariadb.connections.Connection.cursor 32 | 33 | .. automethod:: mariadb.connections.Connection.dump_debug_info 34 | 35 | *Since version 1.1.2.* 36 | 37 | .. automethod:: mariadb.connections.Connection.get_server_version 38 | 39 | .. automethod:: mariadb.connections.Connection.escape_string 40 | 41 | *Since version 1.0.5.* 42 | 43 | .. testcode:: 44 | import mariadb 45 | 46 | # connection parameters 47 | conn_params= { 48 | "user" : "example_user", 49 | "password" : "GHbe_Su3B8", 50 | "host" : "localhost" 51 | } 52 | 53 | with mariadb.connect(**conn_params) as connection: 54 | string = 'This string contains the following special characters: \\,"' 55 | print(connection.escape_string(string)) 56 | 57 | **Output:** 58 | 59 | .. testoutput:: 60 | 61 | This string contains the following special characters: \\,\" 62 | 63 | .. automethod:: mariadb.connections.Connection.kill 64 | 65 | .. note:: 66 | A thread_id from other connections can be determined by executing the SQL statement ``SHOW PROCESSLIST``. 67 | The thread_id of the current connection is stored in the :data:`connection_id` attribute. 68 | 69 | .. automethod:: mariadb.connections.Connection.ping 70 | 71 | .. automethod:: mariadb.connections.Connection.reconnect 72 | 73 | .. automethod:: mariadb.connections.Connection.reset 74 | 75 | .. automethod:: mariadb.connections.Connection.rollback 76 | 77 | .. automethod:: mariadb.connections.Connection.select_db 78 | 79 | *Since version 1.1.0.* 80 | 81 | .. automethod:: mariadb.connections.Connection.show_warnings 82 | 83 | .. automethod:: mariadb.connections.Connection.tpc_begin 84 | 85 | .. automethod:: mariadb.connections.Connection.tpc_commit 86 | 87 | .. automethod:: mariadb.connections.Connection.tpc_prepare 88 | 89 | .. automethod:: mariadb.connections.Connection.tpc_recover 90 | 91 | .. automethod:: mariadb.connections.Connection.tpc_rollback 92 | 93 | --------------------- 94 | Connection attributes 95 | --------------------- 96 | 97 | .. autoattribute:: mariadb.connections.Connection.auto_reconnect 98 | 99 | .. autoattribute:: mariadb.connections.Connection.autocommit 100 | 101 | .. autoattribute:: mariadb.connections.Connection.character_set 102 | 103 | .. autoattribute:: mariadb.connections.Connection.client_capabilities 104 | 105 | *Since version 1.1.0.* 106 | 107 | .. autoattribute:: mariadb.connections.Connection.collation 108 | 109 | .. autoattribute:: mariadb.connections.Connection.connection_id 110 | 111 | .. autoattribute:: mariadb.connections.Connection.database 112 | 113 | .. autoattribute:: mariadb.connections.Connection.open 114 | 115 | *Since version 1.1.0.* 116 | 117 | .. autoattribute:: mariadb.connections.Connection.server_capabilities 118 | 119 | *Since version 1.1.0.* 120 | 121 | .. autoattribute:: mariadb.connections.Connection.extended_server_capabilities 122 | 123 | *Since version 1.1.0.* 124 | 125 | .. autoattribute:: mariadb.connections.Connection.server_info 126 | 127 | .. autoattribute:: mariadb.connections.Connection.server_name 128 | 129 | .. autoattribute:: mariadb.connections.Connection.server_port 130 | 131 | .. autoattribute:: mariadb.connections.Connection.server_status 132 | 133 | *Since version 1.1.0.* 134 | 135 | .. autoattribute:: mariadb.connections.Connection.server_version 136 | 137 | .. autoattribute:: mariadb.connections.Connection.server_version_info 138 | 139 | .. autoattribute:: mariadb.connections.Connection.tls_cipher 140 | 141 | *Since version 1.0.5.* 142 | 143 | .. autoattribute:: mariadb.connections.Connection.tls_version 144 | 145 | .. autoattribute:: mariadb.connections.Connection.tls_peer_cert_info 146 | 147 | *Since version 1.1.11.* 148 | 149 | .. autoattribute:: mariadb.connections.Connection.unix_socket 150 | 151 | .. autoattribute:: mariadb.connections.Connection.user 152 | 153 | .. autoattribute:: mariadb.connections.Connection.warnings 154 | 155 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | .. automodule:: mariadb.constants.CAPABILITY 17 | 18 | *Since version 1.1.4* 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 | with mariadb.connect(**conn_params) as connection: 33 | # test if LOAD DATA LOCAL INFILE is supported 34 | if connection.server_capabilities & CAPABILITY.LOCAL_FILES: 35 | print("Server supports LOCAL INFILE") 36 | 37 | *Output*: 38 | 39 | .. testoutput:: 40 | 41 | Server supports LOCAL INFILE 42 | 43 | 44 | -------------- 45 | CLIENT 46 | -------------- 47 | 48 | .. automodule:: mariadb.constants.CLIENT 49 | 50 | *Since version 1.1.0, deprecated in 1.1.4* 51 | 52 | -------------- 53 | CURSOR 54 | -------------- 55 | 56 | .. automodule:: mariadb.constants.CURSOR 57 | 58 | *Since version 1.1.0* 59 | 60 | .. py:data:: CURSOR.NONE 61 | 62 | This is the default setting (no cursor) 63 | 64 | .. py:data:: CURSOR.READ_ONLY 65 | 66 | Will create a server side read only cursor. The cursor is a forward cursor, which 67 | means it is not possible to scroll back. 68 | 69 | -------------- 70 | ERR (Error) 71 | -------------- 72 | 73 | Using ERR constants instead of error numbers make the code more readable. Error constants 74 | are defined in constants.ERR module 75 | 76 | *Since version 1.1.2* 77 | 78 | .. testcode:: 79 | 80 | import mariadb 81 | from mariadb.constants import * 82 | 83 | # connection parameters 84 | conn_params= { 85 | "user" : "example_user", 86 | "password" : "wrong_password", 87 | "host" : "localhost" 88 | } 89 | 90 | # try to establish a connection 91 | try: 92 | connection= mariadb.connect(**conn_params) 93 | except mariadb.OperationalError as Err: 94 | if Err.errno == ERR.ER_ACCESS_DENIED_ERROR: 95 | print("Access denied. Wrong password!") 96 | 97 | *Output*: 98 | 99 | .. testoutput:: 100 | 101 | Access denied. Wrong password! 102 | 103 | -------------- 104 | FIELD_FLAG 105 | -------------- 106 | 107 | .. automodule:: mariadb.constants.FIELD_FLAG 108 | 109 | *Since version 1.1.0* 110 | 111 | 112 | .. py:data:: FIELD_FLAG.NOT_NULL 113 | 114 | column is defined as not NULL 115 | 116 | .. py:data:: FIELD_FLAG.PRIMARY_KEY 117 | 118 | column is (part of) a primary key 119 | 120 | .. py:data:: FIELD_FLAG.UNIQUE_KEY 121 | 122 | column is (part of) a unique key 123 | 124 | .. py:data:: FIELD_FLAG.MULTIPLE_KEY 125 | 126 | column is (part of) a key 127 | 128 | .. py:data:: FIELD_FLAG.BLOB 129 | 130 | column contains a binary object 131 | 132 | .. py:data:: FIELD_FLAG.UNSIGNED 133 | 134 | numeric column is defined as unsigned 135 | 136 | .. py:data:: FIELD_FLAG.ZEROFILL 137 | 138 | column has zerofill attribute 139 | 140 | .. py:data:: FIELD_FLAG.BINARY 141 | 142 | column is a binary 143 | 144 | .. py:data:: FIELD_FLAG.ENUM 145 | 146 | column is defined as enum 147 | 148 | .. py:data:: FIELD_FLAG.AUTO_INCREMENT 149 | 150 | column is an auto_increment column 151 | 152 | .. py:data:: FIELD_FLAG.TIMESTAMP 153 | 154 | column is defined as time stamp 155 | 156 | .. py:data:: FIELD_FLAG.SET 157 | 158 | column is defined as SET 159 | 160 | .. py:data:: FIELD_FLAG.NO_DEFAULT 161 | 162 | column hasn't a default value 163 | 164 | .. py:data:: FIELD_FLAG.ON_UPDATE_NOW 165 | 166 | column will be set to current timestamp on UPDATE 167 | 168 | .. py:data:: FIELD_FLAG.NUMERIC 169 | 170 | column contains numeric value 171 | 172 | .. py:data:: FIELD_FLAG.PART_OF_KEY 173 | 174 | column is part of a key 175 | 176 | ---------- 177 | FIELD_TYPE 178 | ---------- 179 | 180 | .. automodule:: mariadb.constants.FIELD_TYPE 181 | 182 | .. py:data:: FIELD_TYPE.TINY 183 | 184 | column type is TINYINT (1-byte integer) 185 | 186 | .. py:data:: FIELD_TYPE.SHORT 187 | 188 | column type is SMALLINT (2-byte integer) 189 | 190 | .. py:data:: FIELD_TYPE.LONG 191 | 192 | column tyoe is INT (4-byte integer) 193 | 194 | .. py:data:: FIELD_TYPE.FLOAT 195 | 196 | column type is FLOAT (4-byte single precision) 197 | 198 | .. py:data:: FIELD_TYPE.DOUBLE 199 | 200 | column type is DOUBLE (8-byte double precision) 201 | 202 | .. py:data:: FIELD_TYPE.NULL 203 | 204 | column type is NULL 205 | 206 | .. py:data:: FIELD_TYPE.TIMESTAMP 207 | 208 | column tyoe is TIMESTAMP 209 | 210 | .. py:data:: FIELD_TYPE.LONGLONG 211 | 212 | column tyoe is BIGINT (8-byte Integer) 213 | 214 | .. py:data:: FIELD_TYPE.INT24 215 | 216 | column type is MEDIUMINT (3-byte Integer) 217 | 218 | .. py:data:: FIELD_TYPE.DATE 219 | 220 | column type is DATE 221 | 222 | .. py:data:: FIELD_TYPE.TIME 223 | 224 | column type is TIME 225 | 226 | .. py:data:: FIELD_TYPE.DATETIME 227 | 228 | column type is YEAR 229 | 230 | .. py:data:: FIELD_TYPE.YEAR 231 | 232 | .. py:data:: FIELD_TYPE.VARCHAR 233 | 234 | column type is YEAR 235 | 236 | .. py:data:: FIELD_TYPE.BIT 237 | 238 | column type is BIT 239 | 240 | .. py:data:: FIELD_TYPE.JSON 241 | 242 | column type is JSON 243 | 244 | .. py:data:: FIELD_TYPE.NEWDECIMAL 245 | 246 | column type is DECIMAL 247 | 248 | .. py:data:: FIELD_TYPE.ENUM 249 | 250 | column type is ENUM 251 | 252 | .. py:data:: FIELD_TYPE.SET 253 | 254 | column type is SET 255 | 256 | .. py:data:: FIELD_TYPE.TINY_BLOB 257 | 258 | column type is TINYBLOB (max. length of 255 bytes) 259 | 260 | .. py:data:: FIELD_TYPE.MEDIUM_BLOB 261 | 262 | column type is MEDIUMBLOB (max. length of 16,777,215 bytes) 263 | 264 | .. py:data:: FIELD_TYPE.LONG_BLOB 265 | 266 | column type is LONGBLOB (max. length 4GB bytes) 267 | 268 | .. py:data:: FIELD_TYPE.BLOB 269 | 270 | column type is BLOB (max. length of 65.535 bytes) 271 | 272 | .. py:data:: FIELD_TYPE.VAR_STRING 273 | 274 | column type is VARCHAR (variable length) 275 | 276 | .. py:data:: FIELD_TYPE.STRING 277 | 278 | column type is CHAR (fixed length) 279 | 280 | .. py:data:: FIELD_TYPE.GEOMETRY 281 | 282 | column type is GEOMETRY 283 | 284 | -------------- 285 | INDICATORS 286 | -------------- 287 | 288 | Indicator values are used in executemany() method of cursor class to 289 | indicate special values when connected to a MariaDB server 10.2 or newer. 290 | 291 | .. py:data:: INDICATOR.NULL 292 | 293 | indicates a NULL value 294 | 295 | .. py:data:: INDICATOR.DEFAULT 296 | 297 | indicates to use default value of column 298 | 299 | .. py:data:: INDICATOR.IGNORE 300 | 301 | indicates to ignore value for column for UPDATE statements. 302 | If set, the column will not be updated. 303 | 304 | .. py:data:: INDICATOR.IGNORE_ROW 305 | 306 | indicates not to update the entire row. 307 | 308 | --------------- 309 | INFO 310 | --------------- 311 | 312 | For internal use only 313 | 314 | --------------- 315 | TPC_STATE 316 | --------------- 317 | 318 | For internal use only 319 | 320 | 321 | --------------- 322 | STATUS 323 | --------------- 324 | The STATUS constants are used to check the server status of the current connection. 325 | 326 | *Since version 1.1.0* 327 | 328 | Example: 329 | 330 | .. code-block:: python 331 | 332 | cursor.callproc("my_storedprocedure", (1,"foo")) 333 | 334 | if (connection.server_status & STATUS.SP_OUT_PARAMS): 335 | print("retrieving output parameters from store procedure") 336 | ... 337 | else: 338 | print("retrieving data from stored procedure") 339 | .... 340 | 341 | 342 | .. py:data:: STATUS.IN_TRANS 343 | 344 | Pending transaction 345 | 346 | .. py:data:: STATUS.AUTOCOMMIT 347 | 348 | Server operates in autocommit mode 349 | 350 | .. py:data:: STATUS.MORE_RESULTS_EXIST 351 | 352 | The result from last executed statement contained two or more result 353 | sets which can be retrieved by cursors nextset() method. 354 | 355 | .. py:data:: STATUS.QUERY_NO_GOOD_INDEX_USED 356 | 357 | The last executed statement didn't use a good index. 358 | 359 | .. py:data:: STATUS.QUERY_NO_INDEX_USED 360 | 361 | The last executed statement didn't use an index. 362 | 363 | .. py:data:: STATUS.CURSOR_EXISTS 364 | 365 | The last executed statement opened a server side cursor. 366 | 367 | .. py:data:: STATUS.LAST_ROW_SENT 368 | 369 | For server side cursors this flag indicates end of a result set. 370 | 371 | .. py:data:: STATUS.DB_DROPPED 372 | 373 | The current database in use was dropped and there is no default 374 | database for the connection anymore. 375 | 376 | .. py:data:: STATUS.NO_BACKSLASH_ESCAPES 377 | 378 | Indicates that SQL mode NO_BACKSLASH_ESCAPE is active, which means 379 | that the backslash character '\' becomes an ordinary character. 380 | 381 | .. py:data:: STATUS.QUERY_WAS_SLOW 382 | 383 | The previously executed statement was slow (and needs to be optimized). 384 | 385 | .. py:data:: STATUS.PS_OUT_PARAMS 386 | 387 | The current result set contains output parameters of a stored procedure. 388 | 389 | .. py:data:: STATUS.SESSION_STATE_CHANGED 390 | 391 | The session status has been changed. 392 | 393 | .. py:data:: STATUS.ANSI_QUOTES 394 | 395 | SQL mode ANSI_QUOTES is active, 396 | 397 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | *Since version 1.1.0* 103 | 104 | The parameter table_name, original_column_name and original_table_name are an 105 | extension to the PEP-249 DB API standard. 106 | 107 | .. code-block:: python 108 | 109 | if cursor.description[0][1] == FIELD_TYPE.BLOB: 110 | if cursor.description[0][7] == FIELD_FLAG.BINARY: 111 | print("column is BLOB") 112 | else: 113 | print("column is TEXT") 114 | 115 | 116 | .. autoattribute:: mariadb.cursors.Cursor.lastrowid 117 | 118 | .. autoattribute:: mariadb.cursors.Cursor.metadata 119 | 120 | *Since version 1.1.8* 121 | 122 | .. autoattribute:: mariadb.cursors.Cursor.sp_outparams 123 | 124 | 125 | .. autoattribute:: mariadb.cursors.Cursor.paramcount 126 | 127 | *Since version 1.1.0* 128 | 129 | .. autoattribute:: mariadb.cursors.Cursor.rowcount 130 | 131 | .. note:: 132 | 133 | For unbuffered cursors (default) the exact number of rows can only be 134 | determined after all rows were fetched. 135 | 136 | Example: 137 | 138 | .. code-block:: python 139 | 140 | >>> cursor=conn.cursor() 141 | >>> cursor.execute("SELECT 1") 142 | >>> cursor.rowcount 143 | -1 144 | >>> rows= cursor.fetchall() 145 | >>> cursor.rowcount 146 | 1 147 | >>> cursor=conn.cursor(buffered=True) 148 | >>> cursor.execute("SELECT 1") 149 | >>> cursor.rowcount 150 | 1 151 | 152 | .. autoattribute:: mariadb.cursors.Cursor.statement 153 | 154 | .. autoattribute:: mariadb.cursors.Cursor.warnings 155 | 156 | .. note:: 157 | 158 | Warnings can be retrieved by the show_warnings() method of connection class. 159 | 160 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | Error: "Python.h: No such file or directory" 15 | -------------------------------------------- 16 | 17 | The header files and libraries of the Python development package weren't properly installed. 18 | Use your package manager to install them system-wide: 19 | 20 | **Alpine (using apk):** 21 | 22 | .. code-block:: console 23 | 24 | sudo apk add python3-dev 25 | 26 | **Ubuntu/Debian (using apt):** 27 | 28 | .. code-block:: console 29 | 30 | sudo apt-get install python3-dev 31 | 32 | **CentOS/RHEL (using yum):** 33 | 34 | .. code-block:: console 35 | 36 | sudo yum install python3-devel 37 | 38 | **Fedora (using dnf):** 39 | 40 | .. code-block:: console 41 | 42 | sudo dnf install python3-devel 43 | 44 | **MacOSX (using homebrew):** 45 | 46 | .. code-block:: console 47 | 48 | brew install mariadb-connector-c 49 | 50 | **OpenSuse (using zypper):** 51 | 52 | .. code-block:: console 53 | 54 | sudo zypper in python3-devel 55 | 56 | Note: The python3 development packages of your distribution might not cover all minor versions 57 | of python3. If you are using python3.10 you may need to install python3.10-dev. 58 | 59 | 60 | ModuleNotFoundError: No module named 'packaging' 61 | ------------------------------------------------- 62 | 63 | With deprecation of distutils (see :PEP:`632`) version functions of distutils module were 64 | replaced in |MCP| 1.1.5 by packaging version functions. 65 | 66 | Before you can install |MCP| you have to install the packaging module: 67 | 68 | .. code-block:: console 69 | 70 | pip3 install packaging 71 | 72 | MariaDB Connector/Python requires MariaDB Connector/C >= 3.3.1, found version 3.1.2 73 | -------------------------------------------------------------------------------------- 74 | 75 | The previously installed version of |MCC| is too old and cannot be used for the |MCP| version 76 | you are trying to install. 77 | 78 | To determine the installed version of |MCC|, execute the command: 79 | 80 | .. code-block:: console 81 | 82 | mariadb_config --cc_version 83 | 84 | - Check if your distribution can be upgraded to a more recent version of |MCC|, which fits the requirements. 85 | - If your distribution doesn't provide a recent version of |MCC|, check the |MCDP|, which provides 86 | latest versions for the major distributions. 87 | - If none of the above will work for you, build and install |MCC| from source. 88 | 89 | OSError: mariadb_config not found 90 | ---------------------------------- 91 | 92 | The mariadb_config program is used to retrieve configuration information (such as the location of 93 | header files and libraries, installed version, etc.) from |MCC|. 94 | 95 | This error indicates that |MCC|, an important dependency for client/server communication that needs 96 | to be preinstalled, either was not installed or could not be found. 97 | 98 | * If |MCC| was previously installed, the installation script cannot detect the location of mariadb_config. 99 | Locate the directory where mariadb_config was installed and add this directory to your PATH. 100 | 101 | .. code-block:: console 102 | 103 | # locate mariadb_config 104 | sudo find / -name "mariadb_config" 105 | 106 | * If |MCC| was not installed and the location of mariadb_config couldn't be detected, please install 107 | MariaDB Connector/C. 108 | 109 | Error: struct st_mariadb_methods' has no member named 'db_execute_generate_request' 110 | ----------------------------------------------------------------------------------- 111 | 112 | Even if the correct version of |MCC| was installed, there are multiple mysql.h include files installed 113 | on your system, either from libmysql or an older |MCC| installation. This can be checked by executing: 114 | 115 | .. code-block:: console 116 | 117 | export CFLAGS="-V -E" 118 | pip3 install mariadb > output.txt 119 | 120 | Open output.txt in your favourite editor and search for "search starts here" where you can see the include 121 | files and paths used for the build. 122 | 123 | Q: My distribution doesn't provide a recent version of MariaDB Connector/C 124 | --------------------------------------------------------------------------- 125 | 126 | If your distribution doesn't provide a recent version of |MCC| (required version is |MCC_minversion|) you either 127 | can download a version of |MCC| from the |MCDP| or build the package from source: 128 | 129 | .. code-block:: console 130 | 131 | mkdir bld 132 | cd bld 133 | cmake .. 134 | make 135 | make install 136 | 137 | 138 | Q: Does MariaDB Connector/Python provide pre-releases or snapshot builds which contain recent bug fixes? 139 | -------------------------------------------------------------------------------------------------------- 140 | 141 | No. If an issue was fixed, the fix will be available in the next release via Python's package 142 | manager repository (pypi.org). 143 | 144 | Q: How can I build an actual version from github sources? 145 | ---------------------------------------------------------- 146 | 147 | To build |MCP| from github sources, checkout latest sources from github: 148 | 149 | .. code-block:: console 150 | 151 | git clone https://github.com/mariadb-corporation/mariadb-connector-python.git 152 | 153 | and build and install it with: 154 | 155 | .. code-block:: console 156 | 157 | python3 setup.py build 158 | python3 -m pip install . 159 | 160 | 161 | Connecting 162 | ^^^^^^^^^^ 163 | 164 | mariadb.OperationalError: Can't connect to local server through socket '/tmp/mysql.sock' 165 | ----------------------------------------------------------------------------------------- 166 | 167 | 1. Check if MariaDB server has been started. 168 | 169 | 2. Check if the MariaDB server was correctly configured and uses the right socket file: 170 | 171 | .. code-block:: console 172 | 173 | mysqld --help --verbose | grep socket 174 | 175 | If the socket is different and cannot be changed, you can specify the socket in your 176 | connection parameters. 177 | 178 | .. code-block:: python 179 | 180 | connection = mariadb.connect(unix_socket="/path_socket/mysql.sock", ....) 181 | 182 | Another option is setting the environment variable MYSQL_UNIX_PORT. 183 | 184 | .. code-block:: console 185 | 186 | export MYSQL_UNIX_PORT=/path_to/mysql.sock 187 | 188 | Q: Which authentication methods are supported by MariaDB Connector/Python? 189 | --------------------------------------------------------------------------- 190 | 191 | |MCP| uses |MCC| for client-server communication. That means all authentication plugins shipped 192 | together with |MCC| can be used for user authentication. 193 | 194 | 195 | General 196 | ^^^^^^^ 197 | 198 | Q: How do I execute multiple statements with cursor.execute()? 199 | -------------------------------------------------------------- 200 | 201 | Since |MCP| uses binary protocol for client-server communication, this feature is not supported yet. 202 | 203 | Q: Does MariaDB Connector/Python work with Python 2.x? 204 | ------------------------------------------------------- 205 | 206 | Python versions which reached their end of life are not officially supported. While |MCP| might still work 207 | with older Python 3.x versions, it doesn't work with Python version 2.x. 208 | 209 | Q: How can I see a transformed statement? Is there a mogrify() method available? 210 | -------------------------------------------------------------------------------- 211 | 212 | No, |MCP| Python uses binary protocol for client/server communication. Before a statement will be executed 213 | it will be parsed and parameter markers which are different than question marks will be replaced by question 214 | marks. Afterwards the statement will be sent together with data to the server. The transformed statement can 215 | be obtained by cursor.statement attribute. 216 | 217 | Example: 218 | 219 | .. code-block:: python 220 | 221 | data = ("Future", 2000) 222 | statement = """SELECT DATE_FORMAT(creation_time, '%h:%m:%s') as time, topic, amount 223 | FROM mytable WHERE topic=%s and id > %s""" 224 | cursor.execute(statement, data) 225 | print(cursor.statement) 226 | 227 | .. code-block:: console 228 | 229 | SELECT DATE_FORMAT(creation_time, '%h:%m:%s') as time, topic, amount FROM mytable WHERE topic=? and id > ? 230 | 231 | Please note, that there is no need to escape '%s' by '%%s' for the time conversion in DATE_FORMAT() function. 232 | 233 | Q: Does MariaDB Connector/Python support paramstyle "pyformat"? 234 | ---------------------------------------------------------------- 235 | 236 | The default paramstyle (see :PEP:`249`) is **qmark** (question mark) for parameter markers. For compatibility 237 | with other drivers |MCP| also supports (and automatically recognizes) the **format** and **pyformat** parameter 238 | styles. 239 | 240 | Mixing different paramstyles within the same query is not supported and will raise an exception. 241 | 242 | 243 | Transactions 244 | ^^^^^^^^^^^^ 245 | 246 | Q: Previously inserted records disappeared after my program finished 247 | --------------------------------------------------------------------- 248 | 249 | Default for autocommit in |MCP| is off, which means every transaction must be committed. 250 | Uncommitted pending transactions are rolled back automatically when the connection is closed. 251 | 252 | .. code-block:: python 253 | 254 | .. code-block:: python 255 | 256 | with mariadb.connect(**conn_params) as conn: 257 | with conn.cursor() as cursor: 258 | cursor.execute("CREATE TABLE t1 (id int, name varchar(20))") 259 | 260 | # insert 261 | data = [(1, "Andy"), (2, "George"), (3, "Betty")] 262 | cursor.executemany("INSERT INTO t1 VALUES (?,?)", data) 263 | 264 | # commit pending transactions 265 | connection.commit() 266 | 267 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | with mariadb.connect(**conn_params) as conn: 15 | with conn.cursor() as cursor: 16 | cursor.execute("CREATE USER IF NOT EXISTS example_user@localhost identified by 'GHbe_Su3B8'") 17 | cursor.execute("grant all on test.* to example_user@localhost") 18 | cursor.execute("DROP TABLE IF EXISTS book") 19 | 20 | |MCP| enables python programs to access MariaDB and MySQL databases, using an API 21 | which is compliant with the Python |DBAPI|. It is written in C and Python and uses 22 | MariaDB Connector/C client library for client server communication. 23 | 24 | .. rubric:: Contents 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | :caption: Contents: 29 | 30 | install 31 | usage 32 | pooling 33 | api 34 | license 35 | bugs 36 | faq 37 | 38 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | .. _installation: 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 | 101 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | {% @marketo/form formId=\"4316\" %} 23 | 24 | -------------------------------------------------------------------------------- /docs/source/module.rst: -------------------------------------------------------------------------------- 1 | The MariaDB Connector/Python module 2 | =================================== 3 | 4 | .. _module: 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 | with mariadb.connect(user="example_user", host="localhost", database="test", password="GHbe_Su3B8") as connection: 30 | print(connection.character_set) 31 | 32 | Output: 33 | 34 | .. testoutput:: 35 | 36 | utf8mb4 37 | 38 | --------------- 39 | Connection Pool 40 | --------------- 41 | 42 | .. autofunction:: mariadb.ConnectionPool(**kwargs) 43 | 44 | ----------------- 45 | Type constructors 46 | ----------------- 47 | 48 | .. autofunction:: mariadb.Binary() 49 | 50 | .. autofunction:: mariadb.Date(year, month, day) 51 | 52 | .. autofunction:: mariadb.DateFromTicks(ticks) 53 | 54 | .. autofunction:: mariadb.Time(hour, minute, second) 55 | 56 | .. autofunction:: mariadb.TimeFromTicks(ticks) 57 | 58 | .. autofunction:: mariadb.Timestamp(year, month, day, hour, minute, second) 59 | 60 | .. autofunction:: mariadb.TimestampFromTicks(ticks) 61 | 62 | Attributes 63 | ---------- 64 | 65 | .. attribute:: apilevel 66 | 67 | String constant stating the supported DB API level. The value for `mariadb` is 68 | ``2.0``. 69 | 70 | .. attribute:: threadsafety 71 | 72 | Integer constant stating the level of thread safety. For `mariadb` the value is 1, 73 | which means threads can share the module but not the connection. 74 | 75 | .. attribute:: paramstyle 76 | 77 | String constant stating the type of parameter marker. For `mariadb` the value is 78 | `qmark`. For compatibility reasons `mariadb` also supports the `format` and 79 | `pyformat` paramstyles with the limitation that they can't be mixed inside a SQL statement. 80 | 81 | .. attribute:: mariadbapi_version 82 | 83 | String constant stating the version of the used MariaDB Connector/C library. 84 | 85 | .. attribute:: client_version 86 | 87 | *Since version 1.1.0* 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 | .. attribute:: client_version_info 94 | 95 | *Since version 1.1.0* 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 | 180 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | .. autoattribute:: mariadb.ConnectionPool.connection_count 26 | 27 | *Since version 1.1.0* 28 | 29 | .. autoattribute:: mariadb.ConnectionPool.max_size 30 | 31 | .. autoattribute:: mariadb.ConnectionPool.pool_size 32 | 33 | .. autoattribute:: mariadb.ConnectionPool.pool_name 34 | 35 | .. autoattribute:: mariadb.ConnectionPool.pool_reset_connection 36 | *Since version 1.1.0* 37 | 38 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 reset before returned to the pool 35 | - **`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. Since 1.1.0 36 | 37 | 38 | 2. Connection parameters 39 | 40 | - In addition to the connection pool specific parameters initialization method of ConnectionPool Class accepts the same parameters as the connect() method of mariadb module. 41 | 42 | *Example*: 43 | 44 | .. testcode:: 45 | 46 | import mariadb 47 | 48 | # connection parameters 49 | conn_params= { 50 | "user" : "example_user", 51 | "password" : "GHbe_Su3B8", 52 | "database" : "test" 53 | } 54 | 55 | # create new pool 56 | with mariadb.ConnectionPool(pool_name="myfirstpool", pool_size=5, **conn_params) as pool: 57 | print("Pool size of '%s': %s" % (pool.pool_name, pool.pool_size)) 58 | 59 | # get a connection from pool 60 | with pool.get_connection() as conn: 61 | 62 | # print the default database for connection 63 | print("Current database: %s" % conn.database) 64 | 65 | *Output*: 66 | 67 | .. testoutput:: 68 | 69 | Pool size of 'myfirstpool': 5 70 | Current database: test 71 | 72 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | with mariadb.connect(**conn_params) as conn: 29 | with conn.cursor() as cursor: 30 | cursor.execute("CREATE OR REPLACE TABLE `countries` (" 31 | "`id` int(10) unsigned NOT NULL AUTO_INCREMENT," 32 | "`name` varchar(50) NOT NULL," 33 | "`country_code` char(3) NOT NULL," 34 | "`capital` varchar(50) DEFAULT NULL," 35 | "PRIMARY KEY (`id`)," 36 | "KEY `name` (`name`)," 37 | "KEY `capital` (`capital`)" 38 | ") ENGINE=InnoDB DEFAULT CHARSET=latin1") 39 | 40 | .. testcode:: 41 | 42 | import mariadb 43 | 44 | # connection parameters 45 | conn_params= { 46 | "user" : "example_user", 47 | "password" : "GHbe_Su3B8", 48 | "host" : "localhost", 49 | "database" : "test" 50 | } 51 | 52 | # Establish a connection 53 | with mariadb.connect(**conn_params) as conn: 54 | with conn.cursor() as cursor: 55 | # Populate countries table with some data 56 | cursor.execute("INSERT INTO countries(name, country_code, capital) VALUES (?,?,?)", 57 | ("Germany", "GER", "Berlin")) 58 | 59 | # retrieve data 60 | cursor.execute("SELECT name, country_code, capital FROM countries") 61 | 62 | # print content 63 | row= cursor.fetchone() 64 | print(*row, sep=' ') 65 | 66 | *Output*: 67 | 68 | .. testoutput:: 69 | 70 | Germany GER Berlin 71 | 72 | 73 | Before MariaDB Connector/Python can be used, the MariaDB Connector/Python module must be 74 | imported. 75 | Once the mariadb module is loaded, a connection to a database server will be established 76 | using the method :func:`~mariadb.connect`. 77 | 78 | In order to be able to communicate with the database server in the form of SQL statements, 79 | a cursor object must be created first. 80 | 81 | The method name cursor may be a little misleading: unlike a cursor in MariaDB that can only 82 | read and return data, a cursor in Python can be used for all types of SQL statements. 83 | 84 | After creating the table mytest, everything is ready to insert some data: Column values 85 | that are to be inserted in the database are identified by place holders, the data is then passed in 86 | the form of a tuple as a second parameter. 87 | 88 | After creating and populating the table mytest the cursor will be used to retrieve the data. 89 | 90 | At the end we free resources and close cursor and connection. 91 | 92 | Passing parameters to SQL statements 93 | #################################### 94 | As shown in previous example, passing parameters to SQL statements happens by using placeholders in the statement. By default 95 | MariaDB Connector/Python uses a question mark as a placeholder, for compatibility reason also %s placeholders are supported. 96 | Passing parameters is supported in methods :func:`~execute` and :func:`~executemany` of the cursor class. 97 | 98 | Since |MCP| uses binary protocol, escaping strings or binary data like in other database drivers is not required. 99 | 100 | .. testcode:: 101 | 102 | import mariadb 103 | 104 | # connection parameters 105 | conn_params= { 106 | "user" : "example_user", 107 | "password" : "GHbe_Su3B8", 108 | "host" : "localhost", 109 | "database" : "test" 110 | } 111 | 112 | # Establish a connection 113 | with mariadb.connect(**conn_params) as conn: 114 | with conn.cursor() as cursor: 115 | sql= "INSERT INTO countries (name, country_code, capital) VALUES (?,?,?)" 116 | data= ("Germany", "GER", "Berlin") 117 | cursor.execute(sql, data) 118 | 119 | conn.commit() 120 | 121 | # delete last entry 122 | sql= "DELETE FROM countries WHERE country_code=?" 123 | data= ("GER",) 124 | cursor.execute(sql, data) 125 | 126 | conn.commit() 127 | 128 | 129 | Often there is a requirement to update, delete or insert multiple records. This could be done be using :func:`~execute` in 130 | 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: 131 | 132 | .. testcode:: python 133 | 134 | import mariadb 135 | 136 | # connection parameters 137 | conn_params= { 138 | "user" : "example_user", 139 | "password" : "GHbe_Su3B8", 140 | "host" : "localhost", 141 | "database" : "test" 142 | } 143 | 144 | # Establish a connection 145 | with mariadb.connect(**conn_params) as connection: 146 | with connection.cursor() as cursor: 147 | sql= "INSERT INTO countries (name, country_code, capital) VALUES (?,?,?)" 148 | 149 | data= [("Ireland", "IE", "Dublin"), 150 | ("Italy", "IT", "Rome"), 151 | ("Malaysia", "MY", "Kuala Lumpur"), 152 | ("France", "FR", "Paris"), 153 | ("Iceland", "IS", "Reykjavik"), 154 | ("Nepal", "NP", "Kathmandu")] 155 | 156 | # insert data 157 | cursor.executemany(sql, data) 158 | 159 | # Since autocommit is off by default, we need to commit last transaction 160 | connection.commit() 161 | 162 | # Instead of 3 letter country-code, we inserted 2 letter country code, so 163 | # let's fix this mistake by updating data 164 | sql= "UPDATE countries SET country_code=? WHERE name=?" 165 | data= [("Ireland", "IRL"), 166 | ("Italy", "ITA"), 167 | ("Malaysia", "MYS"), 168 | ("France", "FRA"), 169 | ("Iceland", "ISL"), 170 | ("Nepal", "NPL")] 171 | cursor.executemany(sql, data) 172 | 173 | # Now let's delete all non European countries 174 | sql= "DELETE FROM countries WHERE name=?" 175 | data= [("Malaysia",), ("Nepal",)] 176 | cursor.executemany(sql, data) 177 | 178 | # by default autocommit is off, so we need to commit 179 | # our transactions 180 | connection.commit() 181 | 182 | 183 | When using executemany(), there are a few restrictions: 184 | - All tuples must have the same types as in first tuple. E.g. the parameter [(1),(1.0)] or [(1),(None)] are invalid. 185 | - Special values like None or column default value needs to be indicated by an indicator. 186 | 187 | Using indicators 188 | **************** 189 | 190 | In certain situations, for example when inserting default values or NULL, special indicators must be used. 191 | 192 | .. testcode:: 193 | 194 | import mariadb 195 | from mariadb.constants import * 196 | 197 | import mariadb 198 | 199 | # connection parameters 200 | conn_params= { 201 | "user" : "example_user", 202 | "password" : "GHbe_Su3B8", 203 | "host" : "localhost", 204 | "database" : "test" 205 | } 206 | 207 | # Establish a connection 208 | with mariadb.connect(**conn_params) as connection: 209 | with connection.cursor() as cursor: 210 | cursor.execute("DROP TABLE IF EXISTS cakes") 211 | cursor.execute("CREATE TABLE cakes(id int, cake varchar(100), price decimal(10,2) default 1.99)") 212 | 213 | sql= "INSERT INTO cakes (id, cake, price) VALUES (?,?,?)" 214 | data= [(1, "Cherry Cake", 2.10), (2, "Apple Cake", INDICATOR.DEFAULT)] 215 | cursor.executemany(sql, data) 216 | 217 | Beside the default indicator which inserts the default value of 1.99, the following indicators are supported: 218 | * INDICATOR.IGNORE: Ignores the value (only update commands) 219 | * INDICATOR.NULL: Value is NULL 220 | * INDICATOR.IGNORE_ROW: Don't update or insert row 221 | 222 | .. note:: 223 | * Mixing different parameter styles is not supported and will raise an exception 224 | * The Python string operator % must not be used. The :func:`~execute` method accepts a tuple or list as second parameter. 225 | * Placeholders between quotation marks are interpreted as a string. 226 | * 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. 227 | * Parameters for :func:`~executemany` need to be passed as a list of tuples. 228 | 229 | Supported Data types 230 | -------------------- 231 | 232 | Several standard python types are converted into SQL types and returned as Python objects when a statement is executed. 233 | 234 | .. list-table:: Supported Data Types 235 | :align: left 236 | :header-rows: 1 237 | 238 | * - Python type 239 | - SQL type 240 | * - None 241 | - NULL 242 | * - Bool 243 | - TINYINT 244 | * - Float, Double 245 | - DOUBLE 246 | * - Decimal 247 | - DECIMAL 248 | * - Long 249 | - TINYINT, SMALLINT, INT, BIGINT 250 | * - String 251 | - VARCHAR, VARSTRING, TEXT 252 | * - ByteArray, Bytes 253 | - TINYBLOB, MEDIUMBLOB, BLOB, LONGBLOB 254 | * - DateTime 255 | - DATETIME 256 | * - Date 257 | - DATE 258 | * - Time 259 | - TIME 260 | * - Timestamp 261 | - TIMESTAMP 262 | 263 | {% @marketo/form formId=\"4316\" %} -------------------------------------------------------------------------------- /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 | 56 | - **`host`** - The host name or IP address of the database server. If MariaDB Connector/Python was built with MariaDB Connector/C 3.3, it is also possible to provide a comma separated list of hosts for simple fail over in case of one or more hosts are not available. 57 | - **`user`, `username`** - The username used to authenticate with the database server 58 | - **`password`, `passwd`** - The password of the given user 59 | - **`database`, `db`** - Database (schema) name to use when connecting with the database server 60 | - **`unix_socket`** - The location of the unix socket file to use instead of using an IP port to connect. If socket authentication is enabled, this can also be used in place of a password. 61 | - **`port`** - Port number of the database server. If not specified, the default value of 3306 will be used. 62 | - **`connect_timeout`** - Connect timeout in seconds 63 | - **`read_timeout`** - Read timeout in seconds 64 | - **`write_timeout`** - Write timeout in seconds 65 | - **`local_infile`** - Enables or disables the use of LOAD DATA LOCAL INFILE statements. 66 | - **`compress`** (default: `False`) - Uses the compressed protocol for client server communication. If the server doesn't support compressed protocol, the default protocol will be used. 67 | - **`init_command`** - Command(s) which will be executed when connecting and reconnecting to the database server 68 | - **`default_file`** - Read options from the specified option file. If the file is an empty string, default configuration file(s) will be used 69 | - **`default_group`** - Read options from the specified group 70 | - **`plugin_dir`** - Directory which contains MariaDB client plugins. 71 | - **`reconnect`** - Enables or disables automatic reconnect. Available since version 1.1.4 72 | - **`ssl_key`** - Defines a path to a private key file to use for TLS. This option requires that you use the absolute path, not a relative path. The specified key must be in PEM format 73 | - **`ssl_cert`** - Defines a path to the X509 certificate file to use for TLS. This option requires that you use the absolute path, not a relative path. The X609 certificate must be in PEM format. 74 | - **`ssl_ca`** - Defines a path to a PEM file that should contain one or more X509 certificates for trusted Certificate Authorities (CAs) to use for TLS. This option requires that you use the absolute path, not a relative path. 75 | - **`ssl_capath`** - Defines a path to a directory that contains one or more PEM files that contains one X509 certificate for a trusted Certificate Authority (CA) 76 | - **`ssl_cipher`** - Defines a list of permitted cipher suites to use for TLS 77 | - **`ssl_crlpath`** - Defines a path to a PEM file that should contain one or more revoked X509 certificates to use for TLS. This option requires that you use the absolute path, not a relative path. 78 | - **`ssl_verify_cert`** - Enables server certificate verification. 79 | - **`ssl`** - The connection must use TLS security, or it will fail. 80 | - **`tls_version`** - A comma-separated list (without whitespaces) of TLS versions. Valid versions are TLSv1.0, TLSv1.1,TLSv1.2 and TLSv1.3. Added in version 1.1.7. 81 | - **`autocommit`** (default: `False`) - Specifies the autocommit settings. True will enable autocommit, False will disable it (default). 82 | - **`converter`** - Specifies a conversion dictionary, where keys are FIELD_TYPE values and values are conversion functions 83 | 84 | """ 85 | if kwargs: 86 | if "pool_name" in kwargs: 87 | if not kwargs["pool_name"] in mariadb._CONNECTION_POOLS: 88 | pool = mariadb.ConnectionPool(**kwargs) 89 | else: 90 | pool = mariadb._CONNECTION_POOLS[kwargs["pool_name"]] 91 | c = pool.get_connection() 92 | return c 93 | 94 | connection = connectionclass(*args, **kwargs) 95 | if not isinstance(connection, mariadb.connections.Connection): 96 | raise mariadb.ProgrammingError("%s is not an instance of " 97 | "mariadb.Connection" % connection) 98 | return connection 99 | 100 | 101 | client_version_info = tuple(int(x, 10) for x in mariadbapi_version.split('.')) 102 | client_version = client_version_info[0] * 10000 +\ 103 | client_version_info[1] * 1000 + client_version_info[2] 104 | -------------------------------------------------------------------------------- /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 thread safety when providing connections to threads. 35 | 36 | The size of a connection pool is configurable at creation time, but cannot be changed afterward. The maximum size of a connection pool is limited to 64 connections. 37 | 38 | Keyword Arguments: 39 | 40 | - **`pool_name`** (``str``) - Name of connection pool 41 | - **`pool_size`** (``int``) - Size of pool. The Maximum allowed number is 64. Default to 5 42 | - **`pool_reset_connection`** (``bool``) - Will reset the connection before returning it to the pool. Default to True. 43 | - **`pool_validation_interval`** (``int``) - Specifies the validation interval in milliseconds after which the status of a connection requested from the pool is checked. A value of 0 means that the status will always be checked. Default to 500 (Added in version 1.1.6) 44 | - **\*\*kwargs** - Optional additional connection arguments, as described in mariadb.connect() method. 45 | 46 | """ 47 | 48 | def __init__(self, *args, **kwargs): 49 | """ 50 | Creates a connection pool class 51 | 52 | :param str pool_name: 53 | Name of connection pool 54 | 55 | :param int pool_size: 56 | Size of pool. If not specified, the default value of 5 will be used. 57 | The Maximum allowed number is 64. 58 | 59 | :param bool pool_reset_connection: 60 | Will reset the connection before returning it to the pool. 61 | The Default value is True. 62 | 63 | :param **kwargs kwargs: 64 | Optional additional connection arguments, as described in 65 | mariadb.connect() method. 66 | """ 67 | self._connections_free = [] 68 | self._connections_used = [] 69 | self._pool_args = {} 70 | self._conn_args = {} 71 | self._lock_pool = _thread.RLock() 72 | self.__closed = 0 73 | 74 | key_words = ["pool_name", "pool_size", "pool_reset_connection", 75 | "pool_validation_interval"] 76 | 77 | # check if pool_name was provided 78 | if kwargs and "pool_name" in kwargs: 79 | 80 | # check if pool_name already exists 81 | if kwargs["pool_name"] in mariadb._CONNECTION_POOLS: 82 | raise mariadb.ProgrammingError("Pool '%s' already exists" 83 | % kwargs["pool_name"]) 84 | else: 85 | raise mariadb.ProgrammingError("No pool name specified") 86 | 87 | # save pool keyword arguments 88 | self._pool_args["name"] = kwargs.get("pool_name") 89 | self._pool_args["size"] = int(kwargs.get("pool_size", 5)) 90 | self._pool_args["reset_connection"] = \ 91 | bool(kwargs.get("pool_reset_connection", True)) 92 | self._pool_args["validation_interval"] = \ 93 | int(kwargs.get("pool_validation_interval", 500)) 94 | 95 | # validate pool size (must be in range between 1 and MAX_POOL_SIZE) 96 | if not (0 < self._pool_args["size"] <= MAX_POOL_SIZE): 97 | raise mariadb.ProgrammingError("Pool size must be in range of " 98 | "1 and %s" % MAX_POOL_SIZE) 99 | 100 | # store pool and connection arguments 101 | self._conn_args = kwargs.copy() 102 | for key in key_words: 103 | if key in self._conn_args: 104 | del self._conn_args[key] 105 | 106 | if len(self._conn_args) > 0: 107 | with self._lock_pool: 108 | # fill connection pool 109 | for i in range(0, self._pool_args["size"]): 110 | try: 111 | connection = mariadb.Connection(**self._conn_args) 112 | except mariadb.Error: 113 | # if an error occurred, close all connections 114 | # and raise exception 115 | for j in reversed(range(0, len(self._connections_free))): 116 | try: 117 | self._connections_free[j].close() 118 | except mariadb.Error: 119 | # connect failed, so we are not 120 | # interested in errors 121 | # from close() method 122 | pass 123 | del self._connections_free[j] 124 | raise 125 | self.add_connection(connection) 126 | 127 | # store connection pool in _CONNECTION_POOLS 128 | mariadb._CONNECTION_POOLS[self._pool_args["name"]] = self 129 | 130 | def _replace_connection(self, connection): 131 | """ 132 | Removes the given connection and adds a new connection. 133 | """ 134 | 135 | if connection: 136 | if connection in self._connections_free: 137 | x = self._connections_free.index(connection) 138 | del self._connections_free[x] 139 | elif connection in self._connections_used: 140 | x = self._connections_used.index(connection) 141 | del self._connections_used[x] 142 | 143 | connection._Connection__pool = None 144 | connection.close() 145 | return self.add_connection() 146 | 147 | def __repr__(self): 148 | if (self.__closed): 149 | return "" % (hex(id(self)),) 151 | else: 152 | return "" % (self.pool_name, hex(id(self))) 154 | 155 | def add_connection(self, connection=None): 156 | """ 157 | Adds a connection object to the connection pool. 158 | 159 | In case that the pool doesn’t have a free slot or is not configured, 160 | a PoolError exception will be raised. 161 | """ 162 | 163 | if not self._conn_args: 164 | raise mariadb.PoolError("Couldn't get configuration for pool %s" % 165 | self._pool_args["name"]) 166 | 167 | if (connection is not None and 168 | not isinstance(connection, mariadb.connections.Connection)): 169 | raise mariadb.ProgrammingError("Passed parameter is not a " 170 | "connection object") 171 | 172 | if connection is None and len(self._conn_args) == 0: 173 | raise mariadb.PoolError("Can't get configuration for pool %s" % 174 | self._pool_args["name"]) 175 | 176 | total = len(self._connections_free + self._connections_used) 177 | if total >= self._pool_args["size"]: 178 | raise mariadb.PoolError("Can't add connection to pool %s: " 179 | "No free slot available (%s)." % 180 | (self._pool_args["name"], 181 | total)) 182 | 183 | with self._lock_pool: 184 | if connection is None: 185 | connection = mariadb.Connection(**self._conn_args) 186 | 187 | connection._Connection__pool = self 188 | connection.__last_used = time.perf_counter_ns() 189 | self._connections_free.append(connection) 190 | return connection 191 | 192 | def get_connection(self): 193 | """ 194 | Returns a connection from the connection pool or raises a PoolError 195 | exception if a connection is not available. 196 | """ 197 | 198 | conn = None 199 | 200 | with self._lock_pool: 201 | for i in range(0, len(self._connections_free)): 202 | conn = self._connections_free[i] 203 | dt = (time.perf_counter_ns() - conn.__last_used) / 1000000 204 | if dt > self._pool_args["validation_interval"]: 205 | try: 206 | conn.ping() 207 | except mariadb.Error: 208 | conn = self._replace_connection(conn) 209 | if not conn: 210 | continue 211 | 212 | conn._used += 1 213 | self._connections_used.append(conn) 214 | idx = self._connections_free.index(conn) 215 | del self._connections_free[idx] 216 | return conn 217 | 218 | raise mariadb.PoolError("No connection available") 219 | 220 | def _close_connection(self, connection): 221 | """ 222 | Returns connection to the pool. Internally used 223 | by connection object. 224 | """ 225 | with self._lock_pool: 226 | 227 | try: 228 | if self._pool_args["reset_connection"]: 229 | connection.reset() 230 | elif connection.server_status & STATUS.IN_TRANS: 231 | connection.rollback() 232 | except mariadb.Error: 233 | self._replace_connection(connection) 234 | 235 | if connection: 236 | if connection in self._connections_used: 237 | x = self._connections_used.index(connection) 238 | del self._connections_used[x] 239 | connection.__last_used = time.perf_counter_ns() 240 | self._connections_free.append(connection) 241 | 242 | def set_config(self, **kwargs): 243 | """ 244 | Sets the connection configuration for the connection pool. 245 | For valid connection arguments, check the mariadb.connect() method. 246 | 247 | Note: This method doesn't create connections in the pool. 248 | To fill the pool, one has to use add_connection() ḿethod. 249 | """ 250 | 251 | self._conn_args = kwargs 252 | 253 | def close(self): 254 | """Closes connection pool and all connections.""" 255 | try: 256 | for c in (self._connections_free + self._connections_used): 257 | c._Connection__pool = None 258 | c.close() 259 | finally: 260 | self._connections_free = None 261 | self._connections_used = None 262 | del mariadb._CONNECTION_POOLS[self._pool_args["name"]] 263 | 264 | @property 265 | def pool_name(self): 266 | """Returns the name of the connection pool.""" 267 | 268 | return self._pool_args["name"] 269 | 270 | @property 271 | def pool_size(self): 272 | """Returns the size of the connection pool.""" 273 | 274 | return self._pool_args["size"] 275 | 276 | @property 277 | def max_size(self): 278 | "Returns the maximum size for connection pools.""" 279 | 280 | return MAX_POOL_SIZE 281 | 282 | @property 283 | def connection_count(self): 284 | "Returns the number of connections in connection pool.""" 285 | 286 | try: 287 | return len(self._connections_free + self._connections_used) 288 | except Exception: 289 | return 0 290 | 291 | @property 292 | def pool_reset_connection(self): 293 | """ 294 | If set to true, the connection will be reset on both client and server 295 | side after .close() method was called 296 | """ 297 | return self._pool_args["reset_connection"] 298 | 299 | @pool_reset_connection.setter 300 | def pool_reset_connection(self, reset): 301 | self._pool_args["reset_connection"] = reset 302 | -------------------------------------------------------------------------------- /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 = 15 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 | 'Programming Language :: Python :: 3.14', 98 | 'Operating System :: Microsoft :: Windows', 99 | 'Operating System :: MacOS', 100 | 'Operating System :: POSIX', 101 | 'Intended Audience :: End Users/Desktop', 102 | 'Intended Audience :: Developers', 103 | 'Intended Audience :: System Administrators', 104 | 'Topic :: Database' 105 | ], 106 | description='Python MariaDB extension', 107 | long_description=long_description, 108 | long_description_content_type='text/markdown', 109 | author=PY_MARIADB_AUTHORS, 110 | license='LGPL 2.1', 111 | url='https://www.github.com/mariadb-corporation/' 112 | 'mariadb-connector-python', 113 | project_urls={ 114 | "Bug Tracker": "https://jira.mariadb.org/", 115 | "Documentation": "https://mariadb-corporation.github.io/" 116 | "mariadb-connector-python/", 117 | "Source Code": "https://www.github.com/mariadb-corporation/" 118 | "mariadb-connector-python", 119 | }, 120 | install_requires=['packaging'], 121 | ext_modules=[Extension('mariadb._mariadb', 122 | ['mariadb/mariadb.c', 123 | 'mariadb/mariadb_codecs.c', 124 | 'mariadb/mariadb_connection.c', 125 | 'mariadb/mariadb_cursor.c', 126 | 'mariadb/mariadb_exception.c', 127 | 'mariadb/mariadb_parser.c'], 128 | define_macros=define_macros, 129 | include_dirs=cfg.includes, 130 | library_dirs=cfg.lib_dirs, 131 | libraries=cfg.libs, 132 | extra_compile_args=cfg.extra_compile_args, 133 | extra_link_args=cfg.extra_link_args, 134 | extra_objects=cfg.extra_objects 135 | )], 136 | py_modules=['mariadb.__init__', 137 | 'mariadb.connectionpool', 138 | 'mariadb.connections', 139 | 'mariadb.constants.CAPABILITY', 140 | 'mariadb.constants.CLIENT', 141 | 'mariadb.constants.CURSOR', 142 | 'mariadb.constants.ERR', 143 | 'mariadb.constants.FIELD_FLAG', 144 | 'mariadb.constants.FIELD_TYPE', 145 | 'mariadb.constants.EXT_FIELD_TYPE', 146 | 'mariadb.constants.INDICATOR', 147 | 'mariadb.constants.INFO', 148 | 'mariadb.constants.STATUS', 149 | 'mariadb.constants.TPC_STATE', 150 | 'mariadb.cursors', 151 | 'mariadb.dbapi20', 152 | 'mariadb.field', 153 | 'mariadb.release_info']) 154 | -------------------------------------------------------------------------------- /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/bbfbb49822593cd3bd4e71cffb2a3254aaed349a/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/bbfbb49822593cd3bd4e71cffb2a3254aaed349a/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 | --------------------------------------------------------------------------------