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