├── .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 | [](https://app.travis-ci.com/mariadb-corporation/mariadb-connector-python)
12 |
13 |
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 |
--------------------------------------------------------------------------------