././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.6004107 pymssql-2.2.11/0000755000000000000000000000000000000000000013244 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.dockerignore0000644000000000000000000000005000000000000015713 0ustar00rootroot00000000000000.git .tox *.venv SQLAlchemy* build dist ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5804107 pymssql-2.2.11/.github/0000755000000000000000000000000000000000000014604 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5844107 pymssql-2.2.11/.github/ISSUE_TEMPLATE/0000755000000000000000000000000000000000000016767 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.github/ISSUE_TEMPLATE/bug_report.md0000644000000000000000000000132400000000000021461 0ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Short example reproducing the problem, see http://sscce.org/ for general guidelines. **Expected behavior** A clear and concise description of what you expected to happen. **Current Behavior** What happens instead of the expected behavior. **Context (Environment)** - What are the OS, Python and FreeTDS versions? - please include the output of the following command: `//python -c "import pymssql; print(pymssql.version_info())"` **Additional context** Add any other context about the problem here. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.github/dependabot.yml0000644000000000000000000000035600000000000017440 0ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: / schedule: interval: "weekly" allow: - dependency-type: direct - package-ecosystem: github-actions directory: / schedule: interval: weekly ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5844107 pymssql-2.2.11/.github/workflows/0000755000000000000000000000000000000000000016641 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.github/workflows/test_linux.yml0000644000000000000000000000701400000000000021564 0ustar00rootroot00000000000000name: Linux on: push: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_macos.yml - .github/workflows/test_windows.yml - .github/workflows/test_linux_aarch64.yml tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' pull_request: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_macos.yml - .github/workflows/test_windows.yml - .github/workflows/test_linux_aarch64.yml jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-20.04] services: SQLServer: image: mcr.microsoft.com/mssql/server:2017-latest env: ACCEPT_EULA: Y SA_PASSWORD: sqlServerPassw0rd ports: - 1433:1433 steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: build & install run: | sudo apt-get install libssl-dev libkrb5-dev python -m pip install --upgrade pip pip install -r dev/requirements-dev.txt python dev/build.py \ --ws-dir=./freetds \ --dist-dir=./dist \ --freetds-version="1.4.9" \ --with-openssl=yes \ --enable-krb5 \ --sdist \ --static-freetds pip install pymssql --no-index -f dist python -c "import pymssql; print(pymssql.version_info())" - name: Test with pytest run: | pip install twine --upgrade twine check dist/* pytest -sv wheels: services: SQLServer: image: mcr.microsoft.com/mssql/server:2017-latest env: ACCEPT_EULA: Y SA_PASSWORD: sqlServerPassw0rd ports: - 1433:1433 runs-on: ubuntu-latest strategy: matrix: arch: [i686, x86_64] manylinux: [manylinux1, manylinux2010, manylinux2014, manylinux_2_28] exclude: - arch: i686 manylinux: manylinux_2_28 steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4 - name: Build and test wheels env: DOCKER_IMAGE: quay.io/pypa/${{ matrix.manylinux }}_${{ matrix.arch }} run: | docker pull $DOCKER_IMAGE docker run --rm --net="host" -w=/io -v `pwd`:/io -e MANYLINUX=${{ matrix.manylinux }} $DOCKER_IMAGE /io/dev/build_manylinux_wheels.sh - name: Archive wheels and sdist uses: actions/upload-artifact@v3 with: name: pymssql-${{ runner.os }}-${{ matrix.manylinux }}_${{ matrix.arch }}-${{ github.sha }} path: dist - name: Publish wheels and sdist if: github.repository_owner == 'pymssql' && startsWith(github.ref, 'refs/tags/v') run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_PYPI_TOKEN}} dist/* - name: Publish wheels and sdist on test.pypi.org if: github.repository_owner == 'pymssql' && github.ref == 'refs/heads/master' run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_TEST_PYPI_TOKEN}} --repository-url=https://test.pypi.org/legacy/ dist/* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.github/workflows/test_linux_aarch64.yml0000644000000000000000000000700300000000000023072 0ustar00rootroot00000000000000name: Linux-aarch64 on: push: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_macos.yml - .github/workflows/test_windows.yml - .github/workflows/test_linux.yml tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' pull_request: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_macos.yml - .github/workflows/test_windows.yml - .github/workflows/test_linux.yml jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-20.04] services: SQLServer: image: mcr.microsoft.com/mssql/server:2017-latest env: ACCEPT_EULA: Y SA_PASSWORD: sqlServerPassw0rd ports: - 1433:1433 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: build & install run: | sudo apt-get update sudo apt-get install libssl-dev libkrb5-dev python -m pip install --upgrade pip pip install -r dev/requirements-dev.txt python dev/build.py \ --ws-dir=./freetds \ --dist-dir=./dist \ --freetds-version="1.4.9" \ --with-openssl=yes \ --enable-krb5 \ --sdist \ --static-freetds pip install pymssql --no-index -f dist python -c "import pymssql; print(pymssql.version_info())" - name: Test with pytest run: | pip install twine --upgrade twine check dist/* pytest -sv wheels: services: SQLServer: image: mcr.microsoft.com/mssql/server:2017-latest env: ACCEPT_EULA: Y SA_PASSWORD: sqlServerPassw0rd ports: - 1433:1433 runs-on: ubuntu-latest strategy: matrix: arch: [aarch64] manylinux: [manylinux2014] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: platforms: arm64 - name: Build and test wheels env: DOCKER_IMAGE: quay.io/pypa/${{ matrix.manylinux }}_${{ matrix.arch }} run: | docker pull $DOCKER_IMAGE docker run --rm --net="host" -w=/io -v `pwd`:/io -e MANYLINUX=${{ matrix.manylinux }} $DOCKER_IMAGE /io/dev/build_manylinux_wheels.sh - name: Archive wheels and sdist uses: actions/upload-artifact@v3 with: name: pymssql-${{ runner.os }}-${{ matrix.manylinux }}_${{ matrix.arch }}-${{ github.sha }} path: dist - name: Publish wheels and sdist if: github.repository_owner == 'pymssql' && startsWith(github.ref, 'refs/tags/v') run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_PYPI_TOKEN}} dist/* - name: Publish wheels and sdist on test.pypi.org if: github.repository_owner == 'pymssql' && github.ref == 'refs/heads/master' run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_TEST_PYPI_TOKEN}} --repository-url=https://test.pypi.org/legacy/ dist/* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.github/workflows/test_macos.yml0000644000000000000000000000556700000000000021542 0ustar00rootroot00000000000000name: macOS on: push: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_linux.yml - .github/workflows/test_windows.yml - .github/workflows/test_linux_aarch64.yml - dev/build_manylinux_wheels.sh tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' pull_request: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_linux.yml - .github/workflows/test_windows.yml - .github/workflows/test_linux_aarch64.yml - dev/build_manylinux_wheels.sh jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] os: [macos-latest] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: build wheel and sdist env: LDFLAGS: -L/usr/local/opt/openssl@1.1/lib CPPFLAGS: -I/usr/local/opt/openssl@1.1/include PKG_CONFIG_PATH: /usr/local/opt/openssl@1.1/lib/pkgconfig run: | pip install --upgrade pip pip install -r dev/requirements-dev.txt pip install delocate python setup.py sdist brew install openssl@1.1 brew install libiconv python dev/build.py \ --ws-dir=./freetds \ --dist-dir=./dist \ --freetds-version="1.4.9" \ --with-openssl=yes \ --sdist \ --static-freetds delocate-listdeps --all dist/*.whl delocate-wheel -v dist/*.whl delocate-listdeps --all dist/*.whl pip install pymssql --no-index -f dist python -c "import pymssql; print(pymssql.version_info())" - name: Test with pytest run: | pip install twine --upgrade twine check dist/* pytest -sv - name: Archive wheels and sdist uses: actions/upload-artifact@v3 with: name: pymssql-${{ runner.os }}-${{ github.sha }} path: dist - name: Publish wheels if: github.repository_owner == 'pymssql' && startsWith(github.ref, 'refs/tags/v') run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_PYPI_TOKEN}} dist/*.whl - name: Publish wheels on test.pypi.org if: github.repository_owner == 'pymssql' && github.ref == 'refs/heads/master' run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_TEST_PYPI_TOKEN}} --repository-url=https://test.pypi.org/legacy/ dist/*.whl ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.github/workflows/test_windows.yml0000644000000000000000000000531200000000000022116 0ustar00rootroot00000000000000name: Windows on: push: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_macos.yml - .github/workflows/test_linux.yml - .github/workflows/test_linux_aarch64.yml - dev/build_manylinux_wheels.sh tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' pull_request: branches: - '**' paths-ignore: - docs/** - ChangeLog* - .github/workflows/test_macos.yml - .github/workflows/test_linux.yml - .github/workflows/test_linux_aarch64.yml - dev/build_manylinux_wheels.sh jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] os: [windows-latest] python-architecture: [x86, x64] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.python-architecture}} - name: Install OpenSSL x86 if: matrix.python-architecture == 'x86' run: choco install openssl --forcex86 --version=1.1.1.2100 - name: Install OpenSSL x64 if: matrix.python-architecture == 'x64' run: choco install openssl - name: Install & build dependencies run: | choco install gperf python -m pip install --upgrade pip pip install -r dev/requirements-dev.txt python dev/build.py --ws-dir=freetds --dist-dir=dist --sdist --freetds-version="1.4.9" pip install pymssql --no-index -f dist python -c "import pymssql; print(pymssql.version_info())" - name: Test with pytest run: | pip install twine --upgrade twine check dist/* pytest -sv - name: Archive wheels and sdist uses: actions/upload-artifact@v3 with: name: pymssql-${{ runner.os }}-${{ matrix.python-architecture }}-${{ github.sha }} path: dist - name: Publish wheels if: github.repository_owner == 'pymssql' && startsWith(github.ref, 'refs/tags/v') run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_PYPI_TOKEN}} dist/*.whl - name: Publish wheels on test.pypi.org if: github.repository_owner == 'pymssql' && github.ref == 'refs/heads/master' run: | pip install twine --upgrade twine upload --skip-existing -u __token__ -p ${{secrets.PYMSSQL_TEST_PYPI_TOKEN}} --repository-url=https://test.pypi.org/legacy/ dist/*.whl ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.gitignore0000644000000000000000000000034000000000000015231 0ustar00rootroot00000000000000build *.c *.so a.out tmp/ tests/tests.cfg *.venv *.pyc .tox dist *.egg *.egg-info docs/_build SQLAlchemy-*/ SQLAlchemy-*.tar.gz freetds/ .idea/ venv/ *~ *.bak .eggs .eric?project *.e4p cython_debug/* src/pymssql/version.h ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.pypirc0000644000000000000000000000033700000000000014556 0ustar00rootroot00000000000000[distutils] index-servers = pypi warehouse testpypi [pypi] repository: https://pypi.python.org/pypi [warehouse] repository: https://upload.pypi.io/legacy/ [testpypi] repository: https://test.pypi.org/legacy/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/.readthedocs.yaml0000644000000000000000000000077100000000000016500 0ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # Optionally build your docs in additional formats such as PDF formats: - epub - pdf # Optionally set the version of Python and requirements required to build your docs python: version: 3.7 install: - requirements: docs/requirements.txt ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/ChangeLog.old0000644000000000000000000011316600000000000015603 0ustar00rootroot00000000000000Change Log ========== Version 2.1.5 - 2020-09-17 - Mikhail Terekhov ============================================= General ------- - Revert deprecation - Support Python-3.8. Update tests for Python-3.8 compatibility. - Use correct language level for building Cython extension. - Fix FreeTDS version checks. Add check for version 7.4. - Use Github Actions for building wheels for Linux, macOS and Windows. - Drop bundled FreeTDS-0.95 binaries. - Unless some critical bug is discovered, this will be the last release with Python2 support. Version 2.1.4 - 2018-08-28 - Alex Hagerman ========================================== General ------- - Allow linkage against FreeTDS (by dropping usage of deprecated ``DBVERSION_80`` symbol.) (GH-432) - Stop using 7.1 as default value for the TDS protocol version used in connections. This is a backward incompatible change and affects connections using both `pymssql` and `_mssql`. Now you need to specify a TDS protocol version explicitly by using any of the supported mechanisms (in descending order of precedence): * Using the ``tds_version`` paramenter of ``pymssql.connect()`` and ``_mssql.connect()`` * A ``TDSVER`` enviromnent variable (see FreeTDS documentation) * A ``freetds.conf`` file (see FreeTDS documentation) - Drop support for versions of FreeTDS older than 0.91. - Accept 7.3 as TDS protocol version when establishing a connection. - Add Python 3.7 support - Drop Python 3.3 support Features -------- - Support for new in SQL Server 2008 ``DATE``, ``TIME`` and ``DATETIME2`` data types (GH-156). The following conditions need to be additionally met so values of these column types can be returned from the database as their native corresponding Python data types instead of as strings: * Underlying FreeTDS must be 0.95 or newer. * TDS protocol version in use must be 7.3 or newer. Thanks Ed Avis for the implementation. (GH-331) Bug fixes --------- - Finish implementation of TDS protocol version 7.3 support by actually accepting ``"7.3"`` as TDS protocol version when establishing a connection. (GH-455) - Fixed and expanded Python data types that can be used to pass ``VARBINARY``, ``BINARY`` and ``IMAGE`` SQL Server Stored Procedures input parameters. (GH-425). Thanks Bill Adams for the fix. - Fix ``tds_version`` ``_mssql`` connection property value for TDS version. 7.1 is actually 7.1 and not 8.0. Version 2.1.3 - 2016-06-22 - Ramiro Morales =========================================== General ------- - Windows official binaries: Rollback changes to Windows binaries we had implemented in pymssql 2.1.2; go back to using: * A statically linked version of FreeTDS (v0.95.95) * No SSL support - Update bundled Linux static version of FreeTDS to v0.95.95. Features -------- - We now publish Linux PEP 513 manylinux wheels on PyPI. Bug fixes --------- - Add support for reporting TDS version 7.3 is in use via the ``tds_version`` property of a ``_mssql``-level connection. Version 2.1.2 - 2016-02-10 - Ramiro Morales =========================================== .. attention:: Windows users: You need to download and install additional DLLs pymssql version 2.1.2 includes a change in the official Windows binaries: FreeTDS isn't statically linked as it happened up to release 2.1.1, as that FreeTDS copy lacked SSL support. Please see http://pymssql.org/en/latest/freetds.html#windows for futher details. We are trying to find a balance between security and convenience and will be evaluating the situation for future releases. Your feedback is greatly welcome. General ------- - Drop support for Python 2.6. - Add support for Python 3.5. - Update shipped Linux FreeTDS static libs to 0.95 Features -------- - Add ability to set TDS protocol version from pymssql when connecting to SQL Server. For the remaining pymssql 2.1.x releases its default value will be 7.1 (GH-323) - Add Dockerfile and a Docker image and instructions on how to use it (GH-258). This could be a convenient way to use pymssql without having to build stuff. See http://pymssql.readthedocs.org/en/latest/intro.html#docker Thanks Marc Abramowitz. - Floating point values are now accepted as Stored Procedure arguments (GH-287). Thanks Runzhou Li (Leo) for the report and Bill Adams for the implementation. - Send pymssql version in the appname TDS protocol login record field when the application doesn't provide one (GH-354) Bug fixes --------- - Fix a couple of very common causes of segmentation faults in presence of network a partition between a pymssql-based app and SQL Server (GH-147, GH-271) Thanks Marc Abramowitz. See also GH-373. - Fix failures and inconsistencies in query parameter interpolation when UTF-8-encoded literals are present (GH-185). Thanks Bill Adams. Also, GH-291. - Fix ``login_timeout`` parameter of ``pymssql.connect()`` (GH-318) - Fixed some cases of ``cursor.rowcont`` having a -1 value after iterating over the value returned by pymssql cursor ``fetchmany()`` and ``fetchone()`` methods (GH-141) - Remove automatic treatment of string literals passed in queries that start with ``'0x'`` as hexadecimal values (GH-286) - Fix build fatal error when using Cython >= 0.22 (GH-311) Documentation ------------- - Add installation instructions. Thanks Marc Abramowitz. - Document DB-API-mandated exceptions. - Enhance ``_mssql.MSSQLStoredProcedure.bind()`` docs. - Enhance description of Azure connections requirements. Internals --------- - Add Appveyor hosted CI setup for running tests on Windows (GH-347) - Travis CI: Use newer, faster, container-based infrastructure. Also, test against more than one FreeTDS version. - Make it possible to build official release files (sdist, wheels) on Travis & AppVeyor. Version 2.1.1 - 2014-11-25 - Ramiro Morales =========================================== Features -------- - Custom message handlers (GH-139) The DB-Library API includes a callback mechanism so applications can provide functions known as *message handlers* that get passed informative messages sent by the server which then can be logged, shown to the user, etc. ``_mssql`` now allows you to install your own *message handlers* written in Python. See the ``_msssql`` examples and reference sections of the documentation for more details. Thanks Marc Abramowitz. - Compatibility with Azure It is now possible to transparently connect to `SQL Server instances`_ accessible as part of the Azure_ cloud services. .. note:: If you need to connect to Azure make sure you use FreeTDS 0.91 or newer. - Customizable per-connection initialization SQL clauses (both in ``pymssql`` and ``_mssql``) (GH-97) It is now possible to customize the SQL statements sent right after the connection is established (e.g. ``'SET ANSI_NULLS ON;'``). Previously it was a hard-coded list of queries. See the ``_mssql.MSSQLConnection`` documentation for more details. Thanks Marc Abramowitz. - Added ability to handle instances of ``uuid.UUID`` passed as parameters for SQL queries both in ``pymssql`` and ``_mssql``. (GH-209) Thanks Marat Mavlyutov. - Allow using `SQL Server autocommit mode`_ from ``pymssql`` at connection opening time. This allows e.g. DDL statements like ``DROP DATABASE`` to be executed. (GH-210) Thanks Marat Mavlyutov. - Documentation: Explicitly mention minimum versions supported of Python (2.6) and SQL Server (2005). - Incremental enhancements to the documentation. .. _SQL Server instances: http://www.windowsazure.com/en-us/services/sql-database/ .. _Azure: https://www.windowsazure.com/ .. _SQL Server autocommit mode: http://msdn.microsoft.com/en-us/library/ms187878%28v=sql.105%29.aspx Bug fixes --------- - Handle errors when calling Stored Procedures via the ``.callproc()`` pymssql cursor method. Now it will raise a DB-API ``DatabaseException``; previously it allowed a ``_mssql.MSSQLDatabaseException`` exception to surface. - Fixes in ``tds_version`` ``_mssql`` connections property value Made it work with TDS protocol version 7.2. (GH-211) The value returned for TDS version 7.1 is still 8.0 for backward compatibility (this is because such feature got added in times when Microsoft documentation labeled the two protocol versions that followed 7.0 as 8.0 and 9.0; later it changed them to 7.1 and 7.2 respectively) and will be corrected in a future release (2.2). - PEP 249 compliance (GH-251) Added type constructors to increase compatibility with other libraries. Thanks Aymeric Augustin. - pymssql: Made handling of integer SP params more robust (GH-237) - Check lower bound value when convering integer values from to Python to SQL (GH-238) Internals --------- - Completed migration of the test suite from nose to py.test. - Added a few more test cases to our suite. - Tests: Modified a couple of test cases so the full suite can be run against SQL Server 2005. - Added testing of successful build of documentation to Travis CI script. - Build process: Cleanup intermediate and ad-hoc anciliary files (GH-231, GH-273) - setup.py: Fixed handling of release tarballs contents so no extraneous files are shipped and the documentation tree is actually included. Also, removed unused code. Version 2.1.0 - 2014-02-25 - `Marc Abramowitz `_ ============================================================================= Features -------- - Sphinx-based documentation (GH-149) Read it online at http://pymssql.org/ Thanks, Ramiro Morales! See: * https://github.com/pymssql/pymssql/pull/149 * https://github.com/pymssql/pymssql/pull/162 * https://github.com/pymssql/pymssql/pull/164 * https://github.com/pymssql/pymssql/pull/165 * https://github.com/pymssql/pymssql/pull/166 * https://github.com/pymssql/pymssql/pull/167 * https://github.com/pymssql/pymssql/pull/169 * https://github.com/pymssql/pymssql/pull/174 * https://github.com/pymssql/pymssql/pull/175 - "Green" support (GH-135) Lets you use pymssql with cooperative multi-tasking systems like gevent and have pymssql call a callback when it is waiting for a response from the server. You can set this callback to yield to another greenlet, coroutine, etc. For example, for gevent, you could do:: def wait_callback(read_fileno): gevent.socket.wait_read(read_fileno) pymssql.set_wait_callback(wait_callback) The above is useful if you're say, running a gunicorn server with the gevent worker. With this callback in place, when you send a query to SQL server and are waiting for a response, you can yield to other greenlets and process other requests. This is super useful when you have high concurrency and/or slow database queries and lets you use less gunicorn worker processes and still handle high concurrency. See https://github.com/pymssql/pymssql/pull/135 - Better error messages. E.g.: For a connection failure, instead of: pymssql.OperationalError: (20009, 'Net-Lib error during Connection refused') the dberrstr is also included, resulting in: pymssql.OperationalError: (20009, 'DB-Lib error message 20009, severity 9:\nUnable to connect: Adaptive Server is unavailable or does not exist\nNet-Lib error during Connection refused\n') See: * https://github.com/pymssql/pymssql/pull/151 In the area of error messages, we also made this change: execute: Raise ColumnsWithoutNamesError when as_dict=True and missing column names (GH-160) because the previous behavior was very confusing; instead of raising an exception, we would just return row dicts with those columns missing. This prompted at least one question on the mailing list (https://groups.google.com/forum/?fromgroups#!topic/pymssql/JoZpmNZFtxM), so we thought it was better to handle this explicitly by raising an exception, so the user would understand what went wrong. See: * https://github.com/pymssql/pymssql/pull/160 * https://github.com/pymssql/pymssql/pull/168 - Performance improvements You are most likely to notice a difference from these when you are fetching a large number of rows. * Reworked row fetching (GH-159) There was a rather large amount of type conversion occuring when fetching a row from pymssql. The number of conversions required have been cut down significantly with these changes. Thanks Damien, Churchill! See: * https://github.com/pymssql/pymssql/pull/158 * https://github.com/pymssql/pymssql/pull/159 * Modify get_row() to use the CPython tuple API (GH-178) This drops the previous method of building up a row tuple and switches to using the CPython API, which allows you to create a correctly sized tuple at the beginning and simply fill it in. This appears to offer around a 10% boost when fetching rows from a table where the data is already in memory. Thanks Damien, Churchill! See: * https://github.com/pymssql/pymssql/pull/178 - MSSQLConnection: Add `with` (context manager) support (GH-171) This adds `with` statement support for MSSQLConnection in the `_mssql` module -- e.g.:: with mssqlconn() as conn: conn.execute_query("SELECT @@version AS version") We already have `with` statement support for the `pymssql` module. See: * https://github.com/pymssql/pymssql/pull/171 - Allow passing in binary data (GH-179) Use the bytesarray type added in Python 2.6 to signify that this is binary data and to quote it accordingly. Also modify the handling of str/bytes types checking the first 2 characters for b'0x' and insert that as binary data. See: * https://github.com/pymssql/pymssql/pull/179 - Add support for binding uuid.UUID instances to stored procedures input params (GH-143) Thanks, Ramiro Morales! See: * https://github.com/pymssql/pymssql/pull/143 * https://github.com/pymssql/pymssql/commit/1689c83878304f735eb38b1c63c31e210b028ea7 - The version number is now stored in one place, in pymssql_version.h This makes it easier to update the version number and not forget any places, like I did with pymssql 2.0.1 * See https://github.com/pymssql/pymssql/commit/fd317df65fa62691c2af377e4661defb721b2699 - Improved support for using py.test as test runner (GH-183) * See: https://github.com/pymssql/pymssql/pull/183 - Improved PEP-8 and pylint compliance Bug Fixes --------- - GH-142 ("Change how ``*.pyx`` files are included in package") - this should prevent pymssql.pyx and _mssql.pyx from getting copied into the root of your virtualenv. Thanks, @Arfrever! * See: https://github.com/pymssql/pymssql/issues/142 - GH-145 ("Prevent error string growing with repeated failed connection attempts.") See: * https://github.com/pymssql/pymssql/issues/145 * https://github.com/pymssql/pymssql/pull/146 - GH-151 ("err_handler: Don't clobber dberrstr with oserrstr") * https://github.com/pymssql/pymssql/pull/151 - GH-152 ("_mssql.pyx: Zero init global last_msg_* vars") See: https://github.com/pymssql/pymssql/pull/152 - GH-177 ("binary columns sometimes are processed as varchar") Better mechanism for pymssql to detect that user is passing binary data. See: https://github.com/pymssql/pymssql/issues/177 - buffer overflow fix (GH-182) * See: https://github.com/pymssql/pymssql/pull/181 * See: https://github.com/pymssql/pymssql/pull/182 - Return uniqueidentifer columns as uuid.UUID objects on Python 3 Version 2.0.1 - 2013-10-27 - `Marc Abramowitz `_ ----------------------------------------------------------------------------- * MANIFEST.in: Add "\*.rst" to prevent install error: "IOError: [Errno 2] No such file or directory: 'ChangeLog_highlights.rst'" Version 2.0.0 - 2013-10-25 - `Marc Abramowitz `_ ----------------------------------------------------------------------------- * First official release of pymssql 2.X (`Cython`_-based code) to `PyPI`_! * Compared to pymssql 1.X, this version offers: * Better performance * Thread safety * Fuller test suite * Support for Python 3 * Continuous integration via `Travis CI`_ * Easier to understand code, due to `Cython`_ .. _PyPI: https://pypi.python.org/pypi/pymssql/2.0.0 .. _Travis CI: https://travis-ci.org/pymssql/pymssql .. _Cython: http://cython.org/ .. _ChangeLog: https://github.com/pymssql/pymssql/blob/master/ChangeLog Version 2.0.0b1-dev-20130403 - 2013-04-03 - Marc Abramowitz -------------------------------------------------------------------------------- * Added tag 2.0.0b1-dev-20130403 for changeset 5d0c980ef8b8 (b2b2748f7f88) * Fix issue 118 ("datetime conversion to sql is not converting sub-seconds correctly") - Pad microseconds to 3 digits so it gets converted correctly. Thanks, Ken Robbins (kenneth.robbins at gmail)! (5d0c980ef8b8) * Make tests/test_queries.py actually run tests. It looked like it was half-finished and not working. This fills it out and makes it work and actually test a few things. (5373541eb899) * setup.py: Make it possible to use `python setup.py test` (3c32acb41251) * Bunch of fixes to eliminate build/install warnings (adb0fc75bfd0, fe6cb9aa5120, 446f0005e638, e8d4b19d87b1, 90b2aa2ea01f, 7bb29af4b22c) * Add `pymssql.get_dbversion` function that wraps the dbversion function in FreeTDS. (1158a5d2be9c) * Add a `get_freetds_version` function (a4286224dcf2) * Fix issue 109 ("Failure to pass Unicode characters to callproc; failing test: tests.test_sprocs.TestCallProcFancy.testCallProcWithUnicodeStringWithRussianCharacters"): Skip test because it fails with some versions of FreeTDS but passes with others. (d05341273673) * Fix issue 116 ("A few tests fail if running on a system that has SQL Server available on port 1433") (0fc4086447fe) * Modify tests/test_config.py to use server='dontnameyourserverthis' when doing various tests so it doesn't try to connect to a SQL Server listening on localhost:1433 (0fc4086447fe) * tox.ini: Add {posargs:-w tests -v} to nosetests invocation so that we can pass arguments to tox -- e.g.: to run only specific tests (a105878d500d) * tox.ini: Add "ipdb" to deps, because the IPython debugger is very nice for debugging why tests are failing (be9ee40156cb) * Fix issue 114 ("Fix SP name handling in threaded test so we can actually run it.") (6ac2b75747ad) * Fix issue 100 (Error when executing setup.py {build,develop} on a system with no setuptools: "name 'STDevelopCmd' is not defined") (5222ee37b2ab) * Issue 45 ("Make SQLAlchemy tests part of our testing process"): Add tests/run_sqlalchemy_tests.py for running the SQLAlchemy test suite with pymssql using the server configured in tests/tests.cfg (999d9dbe791b) * Fix issue 92 ("Cursor fetch* methods return redundant keys for column names and column numbers.") (08ae783880dd) * tests/test_connection_as_dict.py: bug 18 ("FetchAll fails to return rows when running against a connection instantiated with as_dict=True."): Add a test which illustrates that the issue is resolved. (058d761cc761) * Fix issue 60 ("cursor.execute raise UnicodeDecodeError if query and params in unicode"): Add patch from tonal.promsoft and add tests. (49210c03a6cf) * Add *.c to MANIFEST.in so they get included in sdist and end-users don't need to run Cython. (25c1a84aac0c) * Fix issue 56 ("callproc do not accept None and unicode string in parameters"): Add patch from tonal.promsoft and add tests (939eb7939136) * version 2.0.0b1-dev-20130403 Version 2.0.0b1-dev-20130108 - 2013-01-08 - Marc Abramowitz -------------------------------------------------------------------------------- * change: put compiled FreeTDS for Windows in pymmsql source, add build instructions to README, better Visual Studio support (#61) + feature: support hostname, port, tds_version connect params without freetds.config + feature: make pymssql.Cursor.rownumber give accurate results for executemany() + feature: bundle FreeTDS libraries & use static libary includes to avoid most end-user-developers needing to mess with FreeTDS * change: speed up handling of tuples/lists when quoting (dieterv77) - bug #46: better handling for byte strings that don't represent ascii data - bug: custom param handling avoids bugs when '%' is used in the SQL (modulus operator) - bug: fix pymssql.DBAPIType so that comparisons work as expected - bug: fetch*() functions would erroneously raise OperationalError when rows exhausted - bug #47: fix threaded tests crashing - bug #79: fix prevision problem with floats (dieterv77) - bug #14: Add setup.py voodoo that undoes setuptools monkeypatching that causes `pip install` to not work with setuptools unless pyrex is installed. (86a73a19d5bd) - bug #106 (OS X: "Symbol not found: _dbadata" error when importing pymssql): Fix OS X build by modifying setup.py so that on OS X we don't attempt to link with the bundled FreeTDS *Linux* library. (88d15d125586) + feature: Add support for running tox (http://tox.testrun.org/) to test across multiple Python versions. (5fa7a6548b31) - bug #44: Remove test_long_identifiers from test_sqlalchemy.py because SQLAlchemy removed the 30 character limit on identifiers. (6585d44eea33) - feature: Add setup.py voodoo so that Cython can automatially be downloaded if it's not installed instead of an ImportError (bb459dd7fd7e) - bug #105: Link with librt on Unix platforms that have it (like Linux, but not FreeBSD or OS X) to prevent 'undefined symbol: clock_gettime' error when importing pymssql. (2b255b1c035f) Tue Nov 02 09:33:00 2010 Damien Churchill * _mssql.pyx: + feature: add support for nullable ints and nullable bits in stored procedure parameters. + feature: add support for positional parameters in stored procedures. + bugfix: add support for using type subclasses as parameters + bugfix: correctly report incorrect logins. + feature: add support for setting the application name + bugfix: accept more than just the decimal.Decimal type for money and decimal parameters. + bugfix: fix raising exceptions from convert_python_value() + bugfix: fix binding parameters of int type when larger than 2^31 - 1 (raise exception). + bugfix: use sprintf rather than python strings in the msg_handler + bugfix: use sprintf rather than python strings in the err_handler + bugfix: make compatible with Cython 0.13 + feature: remove the trusted parameter to connect() + bugfix: fix issue 15, not setting implicit_transactions on connect + bugfix: fix issue 32, setting the wrong hostname on login * pymssql.pyx: + feature: add initial support for callproc() + feature: add support for setting the application name + bugfix: fix issue #7, thanks has.temp3 + bugfix: fix issue #10, rowcount property being incorrect + bugfix: make compatible with Cython 0.13 + feature: remove the trusted parameter to connect() + feature: add returnvalue property with the result of a callproc() call. + feature: fix raising exceptions when args[0] is not a string * MANIFEST.in: + feature: include the tests + bugfix: include ez_setup.py * setup.py: + bugfix: fix issue #8, ZipFile don't has the attribute 'extractall' error for python2.5 * version 1.9.909 Wed Apr 28 11:10:00 2010 Damien Churchill * MANIFEST.in: + bugfix: fix recursive-include for .pyrex * version 1.9.908 Wed Apr 21 16:02:00 2010 Damien Churchill * MANIFEST.in: + bugfix: include missing .pyrex folder * version 1.9.907 Fri Apr 09 13:16:00 2010 Damien Churchill * setup.py: + bugfix: include hack faking that pyrex is installed to workaround a bug in setuptools. * _mssql.pyx: + bugfix: add support for connecting using "." and "(local)" * pymssql.pyx: + feature: add the output type to be used with callproc() + depreciate: the dsn keyword param to pymssql.connect() + feature: add the get/set_max_connections to pymssql * sqlfront.pxd: + feature: tidy up and remove all unused methods. * version 1.9.906 Mon Nov 23 13:37:00 2009 Damien Churchill * _mssql.pyx: + feature: add support for varbinary types + feature: add support for passing in charset to _quote_data + bugfix: rename MSSQLConnection.next_result to MSSQLConnection.nextresult as before + bugfix: set the charset upon login + feature: rewrite _remove_locale using C types instead, 20x faster + feature: add a charset param to quote_data and relating funcs that allows the charset to be specified for unicode encodes. * pymssql.pyx: + feature: add DSN support that was missing + bugfix: fix rowcount property * sqlfront.pxd: add DBSETLCHARSET * tests: + feature: add test for multiple results * setup.py: + feature: fix building on windows + feature: clean generated C files in the clean command + feature: automatically extract freetds.zip on windows when building * version 1.9.903 Fri Nov 20 13:03:00 2009 Damien Churchill * mssqldbmodule.c: deprecated in favour of _mssql.pyx * pymssql.py: deprecated in favour of pymssql.py + feature: added support for uniqueidentifier types + feature: added support for calling remote procedures programmatically * version 1.9.901 Tue May 12 15:43:00 2009 Andrzej Kukula * mssqldbmodule.c: + bugfix: pymssql didn't return second, third etc. result set in case of multi-result statements, e.g. 'SELECT 1; SELECT 2', thanks Damien Churchill Wed Apr 29 19:31:00 2009 Andrzej Kukula * mssqldbmodule.c: + fixed possible memory leak, thanks Evgeny Cherkashin Tue Apr 23 23:00:00 2009 Andrzej Kukula + bugfix: fixed rare quoting bug in select_db() + feature: added 'max_conn' parameter to pymssql.connect() and _mssql.connect() which defaults to 25, thanks Daniel Watrous * nagios-plugin update - thanks Josselin Mouette : + Include a -P port option, to avoid having to passing it with the host name + Fix the encoding of the comments; utf-8 is the declared encoding of the file and must be followed + Fix a typo in the SQL syntax + Connect explicitly to the "master" database (required since 1.0.0) + Improve perfdata output. * version 1.0.2 Tue Apr 21 22:56:00 2009 Andrzej Kukula * mssqldbmodule.c: + bugfix in format_and_run_query(): query strings were sometimes overwritten with garbage due to DECREF in wrong place; thanks Igor Nazarenko + bugfix in get_result(): if a query batch contained DECLARE or possibly other T-SQL statements, no results were returned thanks Kay Schluehr + bugfix in execute_scalar(): check if there are any columns in result + bugfix: check for FAIL after each dbnextrow() + feature: Add support for bigint - #2660972; thanks Alexandr Zamaraev * pymssql.c: + bugfix in execute(): if execute is called without second argument, don't treat '%' in query string as formatting character; restored compatibility with common sense and with pymssql < 1.0.0; thanks Corey Bertram , Wes McKinney + feature: it is possible to specify 'as_dict' to pymssql.connect and rows will be returned as dictionaries instead of tuples; thanks Daniel Watrous Thu Jan 30 18:36:00 2009 Andrzej Kukula * mssqldbmodule.c: + Pyssize_t error on x64 - thanks Josselin Mouette + critical charset updates, thanks Josselin Mouette + more Py_ssize_t updates, further code cleanups + fixed some compiler warnings * pymssql.py: + execute() failed, thanks Josselin Mouette + critical charset updates, thanks Josselin Mouette + removed warnings, users don't want them and they are not 'MUST' priority in DB-API spec * nagios-plugin: introducted Nagios plugin, thanks Julien Blache and Josselin Mouette * version 1.0.1 Thu Jan 29 19:23:00 2009 Andrzej Kukula * version 1.0.0 * so many changes I'll not put them here, I'll document changes from now on. Mon Sep 25 20:18:00 2006 Andrzej Kukula * setup.py: fix for Fink (http://Fink.SF.Net) under OS X (thanks Terrence Brannon ) Sun Sep 24 10:44:00 2006 Andrzej Kukula * setup.py: + it can now dynamically determine the path to SQL 2000 Developer Tools, if win32api and win32con modules are available + simple Python version check to prevent most frequently asked question + version 0.8.0 Wed Sep 13 01:20:00 2006 Andrzej Kukula * mssqldbmodule.c: + corrected misspellings in docstrings + fixed segfault on connection close with Python 2.5; thanks Justin Francis * pymssql.py: + fixed two minor DB-API incompatibilities (thanks Matthew Good ) + fixed datetime quoting (thanks Jan Finell ) * pymssql should be able to build on cygwin (thanks rob@robnet.com) * docstring fixes, webpage doc updates Tue May 15 03:18:00 2006 Jooncheol Park * setup.py, PKG-INFO, README: license change to LGPL Wed Mar 15 08:18:00 2006 Andrzej Kukula * pymssql.py: fixed datetime issue (thanks Jan Finell ) Fri Feb 24 16:11:00 2006 Andrzej Kukula * mssqldbmodule.c: fixed typos in docstrings (thanks Konstantin Veretennicov) Tue Dec 27 15:14:00 2005 Andrzej Kukula * mssqldbmodule.c: bug fixes, improvements and cleanups: + implemented set_login_timeout() and set_query_timeout() functions; + eliminated unnecessary ODBC code + cleaned up exception code and improved exception handling, SF bug #1335560 + web page now correctly mentions FreeTDS 0.63 as the minimal required version + stdmsg() method is now deprecated; all errors are concatenated in errmsg() + implemented min_error_severity: all errors at or above that level will raise the exception; if the severity is lower, they will just accumulate in errmsg() + added setting coltype to NUMBER for float types (found by Jakub Labath) * setup.py: + reincarnated ntwdblib.dll which turned out to be redistributable after all; pymssql includes the latest version that allows connecting to SQL 2005; eliminated some stupid notes from the web page and will ease set up process for users * apitest_mssql.py: new file + provided by Jakub Labath, this file performs some basic DB-API compliance tests; it immediately triggered the unicode bug * version 0.7.4 Sat Oct 22 19:41:00 2005 Andrzej Kukula * mssqldbmodule.c: multithreading improvements - from now on pymssql is thread-safe, it releases GIL in proper places; idea and initial patch by John-Peter Lee (thanks very much!) Mon Sep 5 23:29:00 2005 Andrzej Kukula * setup.py: fixed an installation issue regarding importing pymssql that imports _mssql which isn't installed, and blows up with AttributeError... (thanks Vsevolod Stakhov) * version 0.7.3 Mon Sep 5 00:32:00 2005 Andrzej Kukula * version 0.7.2 Sun Sep 4 23:12:00 2005 Andrzej Kukula * mssqldbmodule.c: improvements and cleanups: + improved error handling: if the db function fails, the exception is thrown automatically and immediately; no need to check return value of conn.query(), just catch _mssql.error + improved error handling: it is possible that MS SQL calls message handler twice; now _mssql catches and reports both of them at once + improved error handling: in some cases _mssql.query() returns success but the results are invalid; now it is handled properly (example "SELECT CAST(1234.5678 AS NUMERIC(4,2))") + added proper connection initialization: a number of SET statements are executed upon connection setup to set sensible SQL behaviour; see source for details; one needs to unset them if needed + implemented min_{message|error}_severity as it is in php_mssql to ignore unimportant errors; it's work in progress + new function rmv_lcl() initially by Mark Pettit, to strip locale crap from MONEY values converted to SQLCHAR while generating Decimal object + other small fixes, improvements and janitorial work Tue Aug 30 00:16:00 2005 Andrzej Kukula * mssqldbmodule.c: new features: + large numbers (DECIMAL, NUMERIC, MONEY, SMALLMONEY) are returned as Decimal object -- this helps maintain accuracy; thanks to Mark Pettit for help + COMPUTE clauses are supported (it wouldn't fetch data for those columns before) + ROWID type has been removed from _mssql module + new type DECIMAL to denote Decimal objects in result set Mon Aug 29 21:59:00 2005 Andrzej Kukula * mssqldbmodule.c: some improvements: + BIT values are returned as Python bool objects, suggested by Mark Pettit + close() method returns None on success (not to be used at all) and throws exception on error + fixed use of uninitialized value when parsing SMALLDATETIME + another round of performance improvements in GetRow() - eliminated unnecessary data conversions and unneeded DB-Lib calls + janitorial fixes Mon Aug 22 04:35:00 2005 Andrzej Kukula * mssqldbmodule.c: massive diff: + fixed bug with fetching query results of some data types; found by Mark Pettit + fixed IndexError when query returns no rows; patch by Jakub Labath + rewritten function GetRow() that fetches query results: performance improvements, better handling of result data types; datetime is returned as datetime object instead of string (it's more consistent with other values -- and more pythonic :) + eliminated DetermineRowSize() + cleanups: _mssql_init() further improvements w.r.t. Python API + janitorial fixes + added licensing information * pymssql.py: docstring changed to look nicer with help() * version 0.7.2 Thu Aug 11 02:12:00 2005 Andrzej Kukula * mssqldbmodule.c: improved module init function: added doc string, made compliant with Python 2.0+ module interface (there are no more coredumps on help()) * mssqldbmodule.c: documented that _mssql.connect() is not portable between FreeTDS-dependent platforms and Windows platforms; documented host:port usage Sat Jul 23 14:20:00 2005 Andrzej Kukula * mssqldbmodule.c: eliminated problems with Python exiting upon invalid login credentials with FreeTDS - the culprit was INT_EXIT and FreeTDS setting DBDEAD * mssqldbmodule.c: added better error messages (esp. on Windows) * mssqldbmodule.c: added msg_handler and err_handler debugging * 0.7.1 packages re-released Fri Jul 22 03:19:00 2005 Andrzej Kukula * mssqldbmodule.c: major change; module revamped to support some more builtin Python features; some redundant code removed; memset() removed as there were no benefits but performance decrease * mssqldbmodule.c: help(_mssql) works; help for conn object works too * pymssql.py: _quote: removed escaping backslash -- with MSSQL it is only needed to escape single quotes by duplicating them * pymssql.py: pymssqlCnx class: added a few checks to properly support DB-API 2.0 (see .close() in PEP 249) * version 0.7.1 Wed Jul 20 22:12:00 2005 Andrzej Kukula * mssqldbmodule.c: removed the workaround for date issue; there were more problems than benefits * mssqldbmodule_tds.c: removed * some more cleanups and corrections Tue Jul 19 14:23:00 2005 Andrzej Kukula * mssqldbmodule.c: major change; many portability problems fixed * mssqldbmodule.c: eliminated port setting; this is job for freetds.conf * mssqldbmodule_tds.c: module to get FreeTDS compile-time settings * build fixes; now it builds cleanly on FreeBSD, Linux and Windows * version 0.7.0 Mon Jul 18 15:21:00 2005 Andrzej Kukula * mssqldbmodule.c: fix build on Windows: changed MS_WIN32 to MS_WINDOWS reported by Mirek Rusin * mssqldbmodule.c: many small fixes and cleanups; janitorial fixes; indentation using indent(1L) * ChangeLog fix! 'mysql' was mentioned instead of 'mssql'... Fri Feb 25 02:15:01 2005 Andrzej Kukula * Fix build on Windows with Visual Studio .NET 2003 and MS SQL Server 2000 SP3a * mssqldbmodule.c: Fix compile error with Visual Studio .NET 2003 * mssqldbmodule.c: Add detection/workaround for date issue caused by different dbdatecrack() prototypes * README.freetds: describe dbdatecrack()-related issue Thu Feb 24 02:03:14 2005 Alejandro Dubrovsky * Export column type names * mssqldbmodule.c: Return column type information for headers * Use type information to make cursor.description conform to API 2 2005-02-17 Alejandro Dubrovsky * Apply patch by Rob Nichols to get cursor.description closer to API 2 compliance 2005-02-08 Alejandro Dubrovsky * Message changes in mssqldbmodule.c (typos, grammar, etc) 2005-02-07 Alejandro Dubrovsky * Added ChangeLog * API Change: add 6th parameter 'port' to connect * Don't close connection on cursor close (noted by Alberto Pastore on the sourceforge project page) * Make cursor.fetchone comply with DB-SIG return a tuple, not a list of tuples (report and patch by Chris Curvey) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/ChangeLog.rst0000644000000000000000000004440500000000000015634 0ustar00rootroot00000000000000Recent Changes ============== Version 2.2.11 - 2023-12-03 - Mikhail Terekhov =============================================== General ------- - Use FreeTDS-1.4.9 for official wheels on PyPi. - Add workflow for aarch64 wheel. Thanks to juntangc (fix #692, #759, #791, #819, #826, #858). - Add datetime.date to SQLDATE conversion. - Add encription parameter to connect (fix #797). Bug fixes --------- - Fix version parsing in development. - Add missing `charset` parameter when formatting query (fix #650). - Use four digits for the year in SP args binding (fix #454). - Fix convert_python_value to work with datetime.date (fix #811). Version 2.2.10 - 2023-10-20 - Mikhail Terekhov =============================================== General ------- - Publish Linux wheels for Python-3.12 Version 2.2.9 - 2023-10-13 - Mikhail Terekhov ============================================== General ------- - Use FreeTDS-1.4.3 for official wheels on PyPi (fix #847). - Build wheels for Python-3.12. Thanks to Raphael Jacob (fix #851, #855). - Use manylinux_2_28 instead of manylinux_2_24 when building wheels in GitHub actions. - Fix build with OpenSSL on Windows. Thanks to PrimozGodec (fix #839). Version 2.2.8 - 2023-07-30 - Mikhail Terekhov ============================================== General ------- - Compatibility with Cython. Thanks to matusvalo (Matus Valo) (fix #826). Version 2.2.7 - 2022-11-15 - Mikhail Terekhov ============================================== General ------- - Build wheels for Python-3.6 (fix 787). Version 2.2.6 - 2022-11-12 - Mikhail Terekhov ============================================== General ------- - Build wheels for Python-3.11. - Use FreeTDS-1.3.13 for official wheels on PyPi. - Fix build on Alpine Linux (fix #762). - Fill in result description in cursor.callproc (fix #772). - Add explicit link to krb5 (fix #776), thanks to James Coder. - Some small doc fixes, thanks to guillaumep and Logan Elandt. Version 2.2.5 - 2022-04-12 - Mikhail Terekhov ============================================== General ------- - Added bytes and bytearray to support bulk_copy types, thanks to steve-strickland (#756). - Use FreeTDS-1.3.9 for official wheels on PyPi. - Enable krb5 in Linux wheels, this time for real (#754). Version 2.2.4 - 2022-01-23 - Mikhail Terekhov ============================================= General ------- - Build wheels for Python-3.10 on Linux. - Fix include paths in setup.py. Version 2.2.3 - 2021-12-21 - Mikhail Terekhov ============================================= General ------- - Build wheels for Python-3.10. - Use FreeTDS-1.3.4 for official wheels on PyPi. - Enable krb5 in Linux wheels (#734). - Fix UnicodeEncodeError for non-ascii database name (#484). - Fix pymssql.Binary (#504). - On macOS check for FreeTDS in homebrew prefix when building. - Some documentation changes. Version 2.2.2 - 2021-07-24 - Mikhail Terekhov ============================================= General ------- - Use FreeTDS-1.3 for official wheels on PyPi. - On macOS use delocate to bundle dependencies when building wheels. - Some documentation changes. Version 2.2.1 - 2021-04-15 - Mikhail Terekhov ============================================= General ------- - Publish Linux wheels for the all supported platforms. manylinux1 wheels are not compatible with modern glibc and OpenSSL. - Add readthedocs configuration file. Version 2.2.0 - 2021-04-08 - Mikhail Terekhov ============================================= General ------- - Add Python-3.9 to the build and test matrix. - Drop support for Python2 and Python3 < 3.6. - Use FreeTDS-1.2.18 for official wheels on PyPi. Features -------- - Support bulk copy (#279). Thanks to Simon.StJG (PR-689). - Wheels on PyPI link FreeTDS statically. - Wheels on PyPI linked against OpenSSL. - Convert pymssql to a package. **Potential compatibility issue:** projects using low level *_mssql* module need to import it from *pymssql* first. Bug fixes --------- - Fixed a deadlock caused by a missing release of GIL (#540), thanks to filip.stefanak (PR-541) and Juraj Bubniak (PR-683). - Prevents memory leak on login failure. Thanks to caogtaa and Simon.StJG (PR-690). - Fix check for TDS version (#652 and #669). - Documentation fixes. Thanks to Simon Biggs, Shane Kimble, Simon.StJG and Dale Evans. Internals --------- - Introduce script dev/build.py to build FreeTDS and pymssql wheels. - Simplify setup.py, introduce environment variables to select FreeTDS includes and libraries. Version 2.1.5 - 2020-09-17 - Mikhail Terekhov ============================================= General ------- - Revert deprecation - Support Python-3.8. Update tests for Python-3.8 compatibility. - Use correct language level for building Cython extension. - Fix FreeTDS version checks. Add check for version 7.4. - Use Github Actions for building wheels for Linux, macOS and Windows. - Drop bundled FreeTDS-0.95 binaries. - Unless some critical bug is discovered, this will be the last release with Python2 support. Version 2.1.4 - 2018-08-28 - Alex Hagerman ========================================== General ------- - Drop support for versions of FreeTDS older than 0.91. - Add Python 3.7 support - Drop Python 3.3 support Features -------- - Support for new in SQL Server 2008 ``DATE``, ``TIME`` and ``DATETIME2`` data types (GH-156). The following conditions need to be additionally met so values of these column types can be returned from the database as their native corresponding Python data types instead of as strings: * Underlying FreeTDS must be 0.95 or newer. * TDS protocol version in use must be 7.3 or newer. Thanks Ed Avis for the implementation. (GH-331) Bug fixes --------- - Fix ``tds_version`` ``_mssql`` connection property value for TDS version. 7.1 is actually 7.1 and not 8.0. Version 2.1.3 - 2016-06-22 - Ramiro Morales =========================================== - We now publish Linux PEP 513 manylinux wheels on PyPI. - Windows official binaries: Rollback changes to Windows binaries we had implemented in pymssql 2.1.2; go back to using: * A statically linked version of FreeTDS (v0.95.95) * No SSL support Version 2.1.2 - 2016-02-10 - Ramiro Morales =========================================== .. attention:: Windows users: You need to download and install additional DLLs pymssql version 2.1.2 includes a change in the official Windows binaries: FreeTDS isn't statically linked as it happened up to release 2.1.1, as that FreeTDS copy lacked SSL support. Please see http://pymssql.org/en/latest/freetds.html#windows for futher details. We are trying to find a balance between security and convenience and will be evaluating the situation for future releases. Your feedback is greatly welcome. Features -------- - Add ability to set TDS protocol version from pymssql when connecting to SQL Server. For the remaining pymssql 2.1.x releases its default value will be 7.1 (GH-323) - Add Dockerfile and a Docker image and instructions on how to use it (GH-258). This could be a convenient way to use pymssql without having to build stuff. See http://pymssql.readthedocs.org/en/latest/intro.html#docker Thanks Marc Abramowitz. - Floating point values are now accepted as Stored Procedure arguments (GH-287). Thanks Runzhou Li (Leo) for the report and Bill Adams for the implementation. - Send pymssql version in the appname TDS protocol login record field when the application doesn't provide one (GH-354) Bug fixes --------- - Fix a couple of very common causes of segmentation faults in presence of network a partition between a pymssql-based app and SQL Server (GH-147, GH-271) Thanks Marc Abramowitz. See also GH-373. - Fix failures and inconsistencies in query parameter interpolation when UTF-8-encoded literals are present (GH-185). Thanks Bill Adams. Also, GH-291. - Fix ``login_timeout`` parameter of ``pymssql.connect()`` (GH-318) - Fixed some cases of ``cursor.rowcont`` having a -1 value after iterating over the value returned by pymssql cursor ``fetchmany()`` and ``fetchone()`` methods (GH-141) - Remove automatic treatment of string literals passed in queries that start with ``'0x'`` as hexadecimal values (GH-286) - Fix build fatal error when using Cython >= 0.22 (GH-311) Internals --------- - Add Appveyor hosted CI setup for running tests on Windows (GH-347) - Travis CI: Use newer, faster, container-based infrastructure. Also, test against more than one FreeTDS version. - Make it possible to build official release files (sdist, wheels) on Travis & AppVeyor. Version 2.1.1 - 2014-11-25 - Ramiro Morales =========================================== Features -------- - Custom message handlers (GH-139) The DB-Library API includes a callback mechanism so applications can provide functions known as *message handlers* that get passed informative messages sent by the server which then can be logged, shown to the user, etc. ``_mssql`` now allows you to install your own *message handlers* written in Python. See the ``_msssql`` examples and reference sections of the documentation for more details. Thanks Marc Abramowitz. - Compatibility with Azure It is now possible to transparently connect to `SQL Server instances`_ accessible as part of the Azure_ cloud services. .. note:: If you need to connect to Azure make sure you use FreeTDS 0.91 or newer. - Customizable per-connection initialization SQL clauses (both in ``pymssql`` and ``_mssql``) (GH-97) It is now possible to customize the SQL statements sent right after the connection is established (e.g. ``'SET ANSI_NULLS ON;'``). Previously it was a hard-coded list of queries. See the ``_mssql.MSSQLConnection`` documentation for more details. Thanks Marc Abramowitz. - Added ability to handle instances of ``uuid.UUID`` passed as parameters for SQL queries both in ``pymssql`` and ``_mssql``. (GH-209) Thanks Marat Mavlyutov. - Allow using `SQL Server autocommit mode`_ from ``pymssql`` at connection opening time. This allows e.g. DDL statements like ``DROP DATABASE`` to be executed. (GH-210) Thanks Marat Mavlyutov. - Documentation: Explicitly mention minimum versions supported of Python (2.6) and SQL Server (2005). - Incremental enhancements to the documentation. .. _SQL Server instances: http://www.windowsazure.com/en-us/services/sql-database/ .. _Azure: https://www.windowsazure.com/ .. _SQL Server autocommit mode: http://msdn.microsoft.com/en-us/library/ms187878%28v=sql.105%29.aspx Bug fixes --------- - Handle errors when calling Stored Procedures via the ``.callproc()`` pymssql cursor method. Now it will raise a DB-API ``DatabaseException``; previously it allowed a ``_mssql.MSSQLDatabaseException`` exception to surface. - Fixes in ``tds_version`` ``_mssql`` connections property value Made it work with TDS protocol version 7.2. (GH-211) The value returned for TDS version 7.1 is still 8.0 for backward compatibility (this is because such feature got added in times when Microsoft documentation labeled the two protocol versions that followed 7.0 as 8.0 and 9.0; later it changed them to 7.1 and 7.2 respectively) and will be corrected in a future release (2.2). - PEP 249 compliance (GH-251) Added type constructors to increase compatibility with other libraries. Thanks Aymeric Augustin. - pymssql: Made handling of integer SP params more robust (GH-237) - Check lower bound value when convering integer values from to Python to SQL (GH-238) Internals --------- - Completed migration of the test suite from nose to py.test. - Added a few more test cases to our suite. - Tests: Modified a couple of test cases so the full suite can be run against SQL Server 2005. - Added testing of successful build of documentation to Travis CI script. - Build process: Cleanup intermediate and ad-hoc anciliary files (GH-231, GH-273) - setup.py: Fixed handling of release tarballs contents so no extraneous files are shipped and the documentation tree is actually included. Also, removed unused code. Version 2.1.0 - 2014-02-25 - `Marc Abramowitz `_ ============================================================================= Features -------- - Sphinx-based documentation (GH-149) Read it online at http://pymssql.org/ Thanks, Ramiro Morales! See: * https://github.com/pymssql/pymssql/pull/149 * https://github.com/pymssql/pymssql/pull/162 * https://github.com/pymssql/pymssql/pull/164 * https://github.com/pymssql/pymssql/pull/165 * https://github.com/pymssql/pymssql/pull/166 * https://github.com/pymssql/pymssql/pull/167 * https://github.com/pymssql/pymssql/pull/169 * https://github.com/pymssql/pymssql/pull/174 * https://github.com/pymssql/pymssql/pull/175 - "Green" support (GH-135) Lets you use pymssql with cooperative multi-tasking systems like gevent and have pymssql call a callback when it is waiting for a response from the server. You can set this callback to yield to another greenlet, coroutine, etc. For example, for gevent, you could do:: def wait_callback(read_fileno): gevent.socket.wait_read(read_fileno) pymssql.set_wait_callback(wait_callback) The above is useful if you're say, running a gunicorn server with the gevent worker. With this callback in place, when you send a query to SQL server and are waiting for a response, you can yield to other greenlets and process other requests. This is super useful when you have high concurrency and/or slow database queries and lets you use less gunicorn worker processes and still handle high concurrency. See https://github.com/pymssql/pymssql/pull/135 - Better error messages. E.g.: For a connection failure, instead of: pymssql.OperationalError: (20009, 'Net-Lib error during Connection refused') the dberrstr is also included, resulting in: pymssql.OperationalError: (20009, 'DB-Lib error message 20009, severity 9:\nUnable to connect: Adaptive Server is unavailable or does not exist\nNet-Lib error during Connection refused\n') See: * https://github.com/pymssql/pymssql/pull/151 In the area of error messages, we also made this change: execute: Raise ColumnsWithoutNamesError when as_dict=True and missing column names (GH-160) because the previous behavior was very confusing; instead of raising an exception, we would just return row dicts with those columns missing. This prompted at least one question on the mailing list (https://groups.google.com/forum/?fromgroups#!topic/pymssql/JoZpmNZFtxM), so we thought it was better to handle this explicitly by raising an exception, so the user would understand what went wrong. See: * https://github.com/pymssql/pymssql/pull/160 * https://github.com/pymssql/pymssql/pull/168 - Performance improvements You are most likely to notice a difference from these when you are fetching a large number of rows. * Reworked row fetching (GH-159) There was a rather large amount of type conversion occuring when fetching a row from pymssql. The number of conversions required have been cut down significantly with these changes. Thanks Damien, Churchill! See: * https://github.com/pymssql/pymssql/pull/158 * https://github.com/pymssql/pymssql/pull/159 * Modify get_row() to use the CPython tuple API (GH-178) This drops the previous method of building up a row tuple and switches to using the CPython API, which allows you to create a correctly sized tuple at the beginning and simply fill it in. This appears to offer around a 10% boost when fetching rows from a table where the data is already in memory. Thanks Damien, Churchill! See: * https://github.com/pymssql/pymssql/pull/178 - MSSQLConnection: Add `with` (context manager) support (GH-171) This adds `with` statement support for MSSQLConnection in the `_mssql` module -- e.g.:: with mssqlconn() as conn: conn.execute_query("SELECT @@version AS version") We already have `with` statement support for the `pymssql` module. See: * https://github.com/pymssql/pymssql/pull/171 - Allow passing in binary data (GH-179) Use the bytesarray type added in Python 2.6 to signify that this is binary data and to quote it accordingly. Also modify the handling of str/bytes types checking the first 2 characters for b'0x' and insert that as binary data. See: * https://github.com/pymssql/pymssql/pull/179 - Add support for binding uuid.UUID instances to stored procedures input params (GH-143) Thanks, Ramiro Morales! See: * https://github.com/pymssql/pymssql/pull/143 * https://github.com/pymssql/pymssql/commit/1689c83878304f735eb38b1c63c31e210b028ea7 - The version number is now stored in one place, in pymssql_version.h This makes it easier to update the version number and not forget any places, like I did with pymssql 2.0.1 * See https://github.com/pymssql/pymssql/commit/fd317df65fa62691c2af377e4661defb721b2699 - Improved support for using py.test as test runner (GH-183) * See: https://github.com/pymssql/pymssql/pull/183 - Improved PEP-8 and pylint compliance Bug Fixes --------- - GH-142 ("Change how ``*.pyx`` files are included in package") - this should prevent pymssql.pyx and _mssql.pyx from getting copied into the root of your virtualenv. Thanks, @Arfrever! * See: https://github.com/pymssql/pymssql/issues/142 - GH-145 ("Prevent error string growing with repeated failed connection attempts.") See: * https://github.com/pymssql/pymssql/issues/145 * https://github.com/pymssql/pymssql/pull/146 - GH-151 ("err_handler: Don't clobber dberrstr with oserrstr") * https://github.com/pymssql/pymssql/pull/151 - GH-152 ("_mssql.pyx: Zero init global last_msg_* vars") See: https://github.com/pymssql/pymssql/pull/152 - GH-177 ("binary columns sometimes are processed as varchar") Better mechanism for pymssql to detect that user is passing binary data. See: https://github.com/pymssql/pymssql/issues/177 - buffer overflow fix (GH-182) * See: https://github.com/pymssql/pymssql/pull/181 * See: https://github.com/pymssql/pymssql/pull/182 - Return uniqueidentifer columns as uuid.UUID objects on Python 3 See `ChangeLog.old`_ for older history... .. _PyPI: https://pypi.python.org/pypi/pymssql/2.0.0 .. _Travis CI: https://travis-ci.org/pymssql/pymssql .. _Cython: http://cython.org/ .. _ChangeLog.old: https://github.com/pymssql/pymssql/blob/master/ChangeLog.old ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/Dockerfile0000644000000000000000000000170400000000000015240 0ustar00rootroot00000000000000# -------------------------------------------------------------------------- # This is a Dockerfile to build an Ubuntu 14.04 Docker image with # pymssql and FreeTDS # # Use a command like: # docker build -t pymssql/pymssql . # -------------------------------------------------------------------------- FROM orchardup/python:2.7 MAINTAINER Marc Abramowitz (@msabramo) # Install apt packages RUN apt-get update && apt-get install -y \ freetds-bin \ freetds-common \ freetds-dev RUN pip install Cython RUN pip install ipython RUN pip install SQLAlchemy RUN pip install pandas RUN pip install Alembic RUN pip install pytest pytest-timeout RUN pip install gevent # Add source directory to Docker image # Note that it's beneficial to put this as far down in the Dockerfile as # possible to maximize the chances of being able to use image caching ADD . /opt/src/pymssql/ RUN pip install /opt/src/pymssql CMD ["ipython"] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/LICENSE0000644000000000000000000006350400000000000014261 0ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/MANIFEST.in0000644000000000000000000000006400000000000015002 0ustar00rootroot00000000000000include src/pymssql/version.h include ChangeLog.rst ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.6004107 pymssql-2.2.11/PKG-INFO0000644000000000000000000001125600000000000014346 0ustar00rootroot00000000000000Metadata-Version: 2.1 Name: pymssql Version: 2.2.11 Summary: DB-API interface to Microsoft SQL Server for Python. (new Cython-based version) Author: Damien Churchill Author-email: damoxc@gmail.com Maintainer: Mikhail Terekhov Maintainer-email: termim@gmail.com License: LGPL Project-URL: Documentation, http://pymssql.readthedocs.io Project-URL: Source, https://github.com/pymssql/pymssql Project-URL: Changelog, https://github.com/pymssql/pymssql/blob/master/ChangeLog.rst Keywords: mssql,SQL Server,database,DB-API Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Database Classifier: Topic :: Database :: Database Engines/Servers Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Description-Content-Type: text/x-rst License-File: LICENSE pymssql - DB-API interface to Microsoft SQL Server ================================================== .. image:: https://github.com/pymssql/pymssql/workflows/Linux/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22Linux%22 .. image:: https://github.com/pymssql/pymssql/workflows/macOS/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22macOS%22 .. image:: https://github.com/pymssql/pymssql/workflows/Windows/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22Windows%22 .. image:: http://img.shields.io/pypi/dm/pymssql.svg :target: https://pypi.python.org/pypi/pymssql/ .. image:: http://img.shields.io/pypi/v/pymssql.svg :target: https://pypi.python.org/pypi/pymssql/ A simple database interface for `Python`_ that builds on top of `FreeTDS`_ to provide a Python DB-API (`PEP-249`_) interface to `Microsoft SQL Server`_. .. _Microsoft SQL Server: http://www.microsoft.com/sqlserver/ .. _Python: http://www.python.org/ .. _PEP-249: http://www.python.org/dev/peps/pep-0249/ .. _FreeTDS: http://www.freetds.org/ Detailed information on pymssql is available on the website: `pymssql.readthedocs.io `_ New development is happening on GitHub at: `github.com/pymssql/pymssql `_ There is a Google Group for discussion at: `groups.google.com `_ Getting started =============== pymssql wheels are available from PyPi. To install it run: .. code-block:: bash pip install -U pip pip install pymssql Most of the times this should be all what's needed. The official pymssql wheels bundle a static copy of FreeTDS and have SSL support so they can be used to connect to Azure. .. note:: On some Linux distributions `pip` version is too old to support all the flavors of manylinux wheels, so upgrading `pip` is necessary. An example of such distributions would be Ubuntu 18.04 or Python3.6 module in RHEL8 and CentOS8. Basic example ============= .. code-block:: python conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor(as_dict=True) cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in cursor: print("ID=%d, Name=%s" % (row['id'], row['name'])) conn.close() Recent Changes ============== Version 2.2.11 - 2023-12-03 - Mikhail Terekhov =============================================== General ------- - Use FreeTDS-1.4.9 for official wheels on PyPi. - Add workflow for aarch64 wheel. Thanks to juntangc (fix #692, #759, #791, #819, #826, #858). - Add datetime.date to SQLDATE conversion. - Add encription parameter to connect (fix #797). Bug fixes --------- - Fix version parsing in development. - Add missing `charset` parameter when formatting query (fix #650). - Use four digits for the year in SP args binding (fix #454). - Fix convert_python_value to work with datetime.date (fix #811). Version 2.2.10 - 2023-10-20 - Mikhail Terekhov =============================================== General ------- - Publish Linux wheels for Python-3.12 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/README.rst0000644000000000000000000000462000000000000014735 0ustar00rootroot00000000000000 pymssql - DB-API interface to Microsoft SQL Server ================================================== .. image:: https://github.com/pymssql/pymssql/workflows/Linux/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22Linux%22 .. image:: https://github.com/pymssql/pymssql/workflows/macOS/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22macOS%22 .. image:: https://github.com/pymssql/pymssql/workflows/Windows/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22Windows%22 .. image:: http://img.shields.io/pypi/dm/pymssql.svg :target: https://pypi.python.org/pypi/pymssql/ .. image:: http://img.shields.io/pypi/v/pymssql.svg :target: https://pypi.python.org/pypi/pymssql/ A simple database interface for `Python`_ that builds on top of `FreeTDS`_ to provide a Python DB-API (`PEP-249`_) interface to `Microsoft SQL Server`_. .. _Microsoft SQL Server: http://www.microsoft.com/sqlserver/ .. _Python: http://www.python.org/ .. _PEP-249: http://www.python.org/dev/peps/pep-0249/ .. _FreeTDS: http://www.freetds.org/ Detailed information on pymssql is available on the website: `pymssql.readthedocs.io `_ New development is happening on GitHub at: `github.com/pymssql/pymssql `_ There is a Google Group for discussion at: `groups.google.com `_ Getting started =============== pymssql wheels are available from PyPi. To install it run: .. code-block:: bash pip install -U pip pip install pymssql Most of the times this should be all what's needed. The official pymssql wheels bundle a static copy of FreeTDS and have SSL support so they can be used to connect to Azure. .. note:: On some Linux distributions `pip` version is too old to support all the flavors of manylinux wheels, so upgrading `pip` is necessary. An example of such distributions would be Ubuntu 18.04 or Python3.6 module in RHEL8 and CentOS8. Basic example ============= .. code-block:: python conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor(as_dict=True) cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in cursor: print("ID=%d, Name=%s" % (row['id'], row['name'])) conn.close() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/appveyor.yml0000644000000000000000000002043100000000000015634 0ustar00rootroot00000000000000# version format version: "{build}" skip_tags: false clone_depth: 20 os: Visual Studio 2015 environment: global: # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the # /E:ON and /V:ON options are not enabled in the batch script intepreter # See: http://stackoverflow.com/a/13751649/163740 APPVEYOR_RDP_PASSWORD: "k7H0kTHbum1C" CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\dev\\appveyor\\run_with_env.cmd" FREETDS_VER: 1.00.78 FREETDS_BASE_URL: "https://ci.appveyor.com/api/projects/FreeTDS/freetds/artifacts" FREETDS_BRANCH: "master" FREETDS_JOB: "Environment%%3A%%20PLAT%%3DWin%PYTHON_ARCH%%%2C%%20WIDTH%%3D%PYTHON_ARCH%%%2C%%20VS_VERSION%%3D%VS_VER%%%2C%%20TDSVER%%3D%TDS_VER%" FREETDS_URL: '"%FREETDS_BASE_URL%/vs%VS_VER%_%PYTHON_ARCH%-%FREETDS_BRANCH%_withoutssl.zip?branch=%FREETDS_BRANCH%&job=%FREETDS_JOB%"' TEST_PYPI_USERNAME: secure: hDyq40wCvSUhkSlRUe9ecw== TEST_PYPI_PASSWORD: secure: ve8I3ZkQZBOdEqF59CLt7w== PYPI_USERNAME: secure: hDyq40wCvSUhkSlRUe9ecw== PYPI_PASSWORD: secure: slUdLrTWmxhTw+QN/JcmAA== matrix: - PYTHON: "C:\\Python27" PYTHON_VERSION: "2.7.14" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2008" TDS_VER: "7.0" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python34" PYTHON_VERSION: "3.4.7" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2010" TDS_VER: "7.1" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python35" PYTHON_VERSION: "3.5.4" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2015" TDS_VER: "7.2" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python36" PYTHON_VERSION: "3.6.4" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2015" TDS_VER: "7.2" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python37" PYTHON_VERSION: "3.7.0" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2015" TDS_VER: "7.2" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.14" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2010" TDS_VER: "7.2" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python34-x64" PYTHON_VERSION: "3.4.7" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2010" TDS_VER: "7.2" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python35-x64" PYTHON_VERSION: "3.5.4" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2015" TDS_VER: "7.3" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6.4" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2015" TDS_VER: "7.3" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.0" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2015" TDS_VER: "7.3" INSTANCENAME: "SQL2008R2SP2" - PYTHON: "C:\\Python27" PYTHON_VERSION: "2.7.14" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2008" TDS_VER: "7.0" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python34" PYTHON_VERSION: "3.4.7" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2010" TDS_VER: "7.1" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python35" PYTHON_VERSION: "3.5.4" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2015" TDS_VER: "7.2" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python36" PYTHON_VERSION: "3.6.4" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2015" TDS_VER: "7.2" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python37" PYTHON_VERSION: "3.7.0" PYTHON_ARCH: "32" ARCH: x86 VS_VER: "2015" TDS_VER: "7.2" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.14" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2010" TDS_VER: "7.2" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python34-x64" PYTHON_VERSION: "3.4.7" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2010" TDS_VER: "7.2" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python35-x64" PYTHON_VERSION: "3.5.4" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2015" TDS_VER: "7.3" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6.4" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2015" TDS_VER: "7.3" INSTANCENAME: "SQL2012SP1" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.0" PYTHON_ARCH: "64" ARCH: x86_64 VS_VER: "2015" TDS_VER: "7.3" INSTANCENAME: "SQL2012SP1" install: # If there is a newer build queued for the same PR, cancel this one. # The AppVeyor 'rollout builds' option is supposed to serve the same # purpose but it is problematic because it tends to cancel builds pushed # directly to master instead of just PR builds (or the converse). # credits: JuliaLang developers. - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` throw "There are newer queued builds for this pull request, failing early." } # Install Python (from the official .msi of http://python.org) and pip when # not already installed. - "powershell dev\\appveyor\\install.ps1" # Prepend current Python and scripts dirs to the PATH of this build (this cannot be # done from inside the powershell script as it would require to restart # the parent CMD process). - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" # Upgrade to the latest version of pip to avoid it displaying warnings # about it being out of date. - "python -m pip install --upgrade pip" # Install the build dependencies of the project. If some dependencies contain # compiled extensions and are not provided as pre-built wheel packages, # pip will build them from source using the MSVC compiler matching the # target Python version and architecture - "%CMD_IN_ENV% pip install -r dev\\dev-requirements.pip" #- cmd: if %PYTHON_VERSION:~0,1% == 3 ( %CMD_IN_ENV% pip install gevent==1.1.b6 ) else ( %CMD_IN_ENV% pip install gevent ) - "%CMD_IN_ENV% pip install gevent" before_build: # win-iconv - "powershell dev\\appveyor\\install-win-iconv.ps1" # FreeTDS - "rmdir /s /q freetds\\vs%VS_VER%_%PYTHON_ARCH% || cmd /c \"exit /b 0\"" - curl -L -o vs%VS_VER%_%PYTHON_ARCH%-%FREETDS_BRANCH%_withoutssl.zip %FREETDS_URL% - 7z x -ofreetds vs%VS_VER%_%PYTHON_ARCH%-%FREETDS_BRANCH%_withoutssl.zip - mv freetds/vs%VS_VER%_%PYTHON_ARCH%-%FREETDS_BRANCH% freetds/vs%VS_VER%_%PYTHON_ARCH% build_script: - "%CMD_IN_ENV% pip install ." before_test: - copy dev\appveyor\tests.cfg tests\ # Add relevant OpenSSL DLLs dir to PATH envvar - ps: | if ($env:PYTHON_ARCH -eq 32) { $env:PATH = $env:APPVEYOR_BUILD_FOLDER + "\openssl\bin;" + $env:PATH } else { $env:PATH = $env:APPVEYOR_BUILD_FOLDER + "\openssl\bin64;" + $env:PATH } # Add relevant FreeTDS DLLs dir to PATH envvar - set PATH=%CD%\freetds\vs%VS_VER%_%PYTHON_ARCH%\lib;%PATH% # Start, reconfigure and restart SQL Server - net start MSSQL$%INSTANCENAME% - "powershell dev\\appveyor\\sql-server-activate-tcp-fixed-port.ps1" test_script: - "py.test --junitxml=junit.xml" after_test: # Preserve tests results JUnit XML file - ps: | $url = "https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)" $file = '.\junit.xml' (New-Object 'System.Net.WebClient').UploadFile($url, (Resolve-Path $file)) # If tests are successful, create binary packages for the project. - "%CMD_IN_ENV% pip wheel ." artifacts: - path: dist\*.whl on_success: ps: | if ($env:APPVEYOR_REPO_TAG -eq "true" -or $env:APPVEYOR_REPO_TAG -eq "True") { If ($env:INSTANCENAME -eq "SQL2012SP1") { pip install twine --upgrade # twine upload -u $env:TEST_PYPI_USERNAME -p $env:TEST_PYPI_PASSWORD --config-file dev\appveyor\pypirc -r testpypi $env:APPVEYOR_BUILD_FOLDER\dist\*.whl twine upload -u $env:PYPI_USERNAME -p $env:PYPI_PASSWORD $env:APPVEYOR_BUILD_FOLDER\dist\*.whl } } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5844107 pymssql-2.2.11/dev/0000755000000000000000000000000000000000000014022 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/__init__.py0000644000000000000000000000006700000000000016136 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Development utilities. """ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5884109 pymssql-2.2.11/dev/appveyor/0000755000000000000000000000000000000000000015667 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/appveyor/install-win-iconv.ps10000644000000000000000000000433600000000000021677 0ustar00rootroot00000000000000$ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Function MSVC-Env-Invoke([string] $command) { $command = "$env:COMSPEC /E:ON /V:ON /C $PSScriptRoot\run_with_env.cmd " + $command $output = Invoke-Expression -Command:$command Write-Output $output if ($LastExitCode -ne 0) { exit $LastExitCode } } Function Ensure-Dir([string] $Path) { if (-not (Test-Path -Path $Path)) { New-Item -ItemType Directory -Force -Path $Path | Out-Null } } Function Cached-Download([string] $Url, [string] $OutputFile) { if (-not (Test-Path -Path $OutputFile)) { Write-Verbose "Downloading $OutputFile from '$Url' ..." (New-Object System.Net.WebClient).DownloadFile($Url, $OutputFile) } } set-alias extract "$env:ProgramFiles\7-Zip\7z.exe" # # Build win-iconv. # if (Test-Path env:WIN_ICONV_VERSION) { $win_iconv_version = $env:WIN_ICONV_VERSION } else { $win_iconv_version = "0.0.8" Write-Verbose "WIN_ICONV_VERSION not set; defaulting to $win_iconv_version" } $build_dir = "$PSScriptRoot\..\..\build" if (Test-Path env:BUILD_INSTALL_PREFIX) { $install_prefix = $env:BUILD_INSTALL_PREFIX } else { $install_prefix = $build_dir } Ensure-Dir $build_dir Ensure-Dir "$install_prefix\include" Ensure-Dir "$install_prefix\lib" $url = "https://codeload.github.com/win-iconv/win-iconv/zip/v$win_iconv_version" $win_iconv_zip = "$build_dir\win-iconv-$win_iconv_version.zip" Cached-Download $url $win_iconv_zip $win_iconv_path = "win-iconv-$win_iconv_version" if (-not (Test-Path -Path $win_iconv_path)) { # Requires PowerShell 5+. Write-Verbose "Expanding $win_iconv_zip ..." extract x -aoa "$win_iconv_zip" -o"$build_dir" if ($LastExitCode -ne 0) { exit $LastExitCode } } pushd "$build_dir\$win_iconv_path" $cmake = "`` cmake -G ""NMake Makefiles"" `` -DBUILD_STATIC=on `` -DBUILD_SHARED=off `` -DBUILD_EXECUTABLE=off `` -DBUILD_TEST=on `` -DCMAKE_BUILD_TYPE=Release `` . " MSVC-Env-Invoke $cmake MSVC-Env-Invoke 'nmake' popd # Copy lib and include files to common dir. cp "$build_dir\$win_iconv_path\iconv.h" "$install_prefix\include" cp "$build_dir\$win_iconv_path\*.lib" "$install_prefix\lib" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/appveyor/install.ps10000644000000000000000000001603300000000000017765 0ustar00rootroot00000000000000# Sample script to install Python and pip under Windows # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ $MINICONDA_URL = "http://repo.continuum.io/miniconda/" $BASE_URL = "https://www.python.org/ftp/python/" $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" $GET_PIP_PATH = "C:\get-pip.py" $PYTHON_PRERELEASE_REGEX = @" (?x) (?\d+) \. (?\d+) \. (?\d+) (?[a-z]{1,2}\d+) "@ function Download ($filename, $url) { $webclient = New-Object System.Net.WebClient $basedir = $pwd.Path + "\" $filepath = $basedir + $filename if (Test-Path $filename) { Write-Host "Reusing" $filepath return $filepath } # Download and retry up to 3 times in case of network transient errors. Write-Host "Downloading" $filename "from" $url $retry_attempts = 2 for ($i = 0; $i -lt $retry_attempts; $i++) { try { $webclient.DownloadFile($url, $filepath) break } Catch [Exception]{ Start-Sleep 1 } } if (Test-Path $filepath) { Write-Host "File saved at" $filepath } else { # Retry once to get the error message if any at the last try $webclient.DownloadFile($url, $filepath) } return $filepath } function ParsePythonVersion ($python_version) { if ($python_version -match $PYTHON_PRERELEASE_REGEX) { return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, $matches.prerelease) } $version_obj = [version]$python_version return ($version_obj.major, $version_obj.minor, $version_obj.build, "") } function DownloadPython ($python_version, $platform_suffix) { $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version if (($major -le 2 -and $micro -eq 0) ` -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` ) { $dir = "$major.$minor" $python_version = "$major.$minor$prerelease" } else { $dir = "$major.$minor.$micro" } if ($prerelease) { if (($major -le 2) ` -or ($major -eq 3 -and $minor -eq 1) ` -or ($major -eq 3 -and $minor -eq 2) ` -or ($major -eq 3 -and $minor -eq 3) ` ) { $dir = "$dir/prev" } } if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { $ext = "msi" if ($platform_suffix) { $platform_suffix = ".$platform_suffix" } } else { $ext = "exe" if ($platform_suffix) { $platform_suffix = "-$platform_suffix" } } $filename = "python-$python_version$platform_suffix.$ext" $url = "$BASE_URL$dir/$filename" $filepath = Download $filename $url return $filepath } function InstallPython ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "" } else { $platform_suffix = "amd64" } $installer_path = DownloadPython $python_version $platform_suffix $installer_ext = [System.IO.Path]::GetExtension($installer_path) Write-Host "Installing $installer_path to $python_home" $install_log = $python_home + ".log" if ($installer_ext -eq '.msi') { InstallPythonMSI $installer_path $python_home $install_log } else { InstallPythonEXE $installer_path $python_home $install_log } if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallPythonEXE ($exepath, $python_home, $install_log) { $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" RunCommand $exepath $install_args } function InstallPythonMSI ($msipath, $python_home, $install_log) { $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" $uninstall_args = "/qn /x $msipath" RunCommand "msiexec.exe" $install_args if (-not(Test-Path $python_home)) { Write-Host "Python seems to be installed else-where, reinstalling." RunCommand "msiexec.exe" $uninstall_args RunCommand "msiexec.exe" $install_args } } function RunCommand ($command, $command_args) { Write-Host $command $command_args Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru } function InstallPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $python_path = $python_home + "\python.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) Write-Host "Executing:" $python_path $GET_PIP_PATH & $python_path $GET_PIP_PATH } else { Write-Host "pip already installed." } } function DownloadMiniconda ($python_version, $platform_suffix) { if ($python_version -eq "3.4") { $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" } else { $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" } $url = $MINICONDA_URL + $filename $filepath = Download $filename $url return $filepath } function InstallMiniconda ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "x86" } else { $platform_suffix = "x86_64" } $filepath = DownloadMiniconda $python_version $platform_suffix Write-Host "Installing" $filepath "to" $python_home $install_log = $python_home + ".log" $args = "/S /D=$python_home" Write-Host $filepath $args Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallMinicondaPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $conda_path = $python_home + "\Scripts\conda.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $args = "install --yes pip" Write-Host $conda_path $args Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } else { Write-Host "pip already installed." } } function main () { InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON InstallPip $env:PYTHON } main ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/appveyor/pypirc0000644000000000000000000000033700000000000017123 0ustar00rootroot00000000000000[distutils] index-servers = pypi warehouse testpypi [pypi] repository: https://pypi.python.org/pypi [warehouse] repository: https://upload.pypi.io/legacy/ [testpypi] repository: https://test.pypi.org/legacy/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/appveyor/run_with_env.cmd0000644000000000000000000001004200000000000021060 0ustar00rootroot00000000000000:: To build extensions for 64 bit Python 3, we need to configure environment :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) :: :: To build extensions for 64 bit Python 2, we need to configure environment :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) :: :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific :: environment configurations. :: :: Note: this script needs to be run with the /E:ON and /V:ON flags for the :: cmd interpreter, at least for (SDK v7.0) :: :: More details at: :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows :: http://stackoverflow.com/a/13751649/163740 :: :: Author: Olivier Grisel :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ :: :: Notes about batch files for Python people: :: :: Quotes in values are literally part of the values: :: SET FOO="bar" :: FOO is now five characters long: " b a r " :: If you don't want quotes, don't include them on the right-hand side. :: :: The CALL lines at the end of this file look redundant, but if you move them :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y :: case, I don't know why. @ECHO OFF SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf :: Extract the major and minor versions, and allow for the minor version to be :: more than 9. This requires the version number to have two dots in it. SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% IF "%PYTHON_VERSION:~3,1%" == "." ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% ) ELSE ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% ) :: Based on the Python version, determine what SDK version to use, and whether :: to set the SDK for 64-bit. IF %MAJOR_PYTHON_VERSION% == 2 ( SET WINDOWS_SDK_VERSION="v7.0" SET SET_SDK_64=Y ) ELSE ( IF %MAJOR_PYTHON_VERSION% == 3 ( SET WINDOWS_SDK_VERSION="v7.1" IF %MINOR_PYTHON_VERSION% LEQ 4 ( SET SET_SDK_64=Y ) ELSE ( SET SET_SDK_64=N IF EXIST "%WIN_WDK%" ( :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN "%WIN_WDK%" 0wdf ) ) ) ELSE ( ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" EXIT 1 ) ) IF %PYTHON_ARCH% == 64 ( IF "%VS_VER%" == "2015" ( ECHO Using MSVC 2015 build environment for 64 bit architecture "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 ) ELSE ( IF %SET_SDK_64% == Y ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release ) ELSE ( ECHO Using default MSVC build environment for 64 bit architecture ) ) ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ELSE ( IF "%VS_VER%" == "2008" ( ECHO Using MSVC 2008 build environment for 32 bit architecture "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86 ) ELSE IF "%VS_VER%" == "2010" ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% on a 32 bit architecture "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x86 /release ) ELSE IF "%VS_VER%" == "2015" ( ECHO Using MSVC 2015 build environment for 32 bit architecture "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 ) ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/appveyor/sql-server-activate-tcp-fixed-port.ps10000644000000000000000000000252200000000000025061 0ustar00rootroot00000000000000# Configure SQL server so it accepts TCP connections. # # This Windows PowerShell script is used on the Appveyor hosted CI plaform when # running the FreeTDS test suite but could be useful to automate the task in # other scenarios. # # See # http://www.appveyor.com/docs/services-databases#enabling-tcp-ip-named-pipes-and-setting-instance-alias # https://gist.githubusercontent.com/FeodorFitsner/d971c5a98782d211640d/raw/sql-server-ip-and-alias.ps1 # http://geekswithblogs.net/TedStatham/archive/2014/06/13/setting-the-ports-for-a-named-sql-server-instance-using.aspx [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null $serverName = $env:COMPUTERNAME $instanceName = $env:INSTANCENAME $smo = 'Microsoft.SqlServer.Management.Smo.' $wmi = new-object ($smo + 'Wmi.ManagedComputer') # Enable TCP/IP $uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']" $Tcp = $wmi.GetSmoObject($uri) $Tcp.IsEnabled = $true foreach ($ipAddress in $Tcp.IPAddresses) { $ipAddress.IPAddressProperties["TcpDynamicPorts"].Value = "" $ipAddress.IPAddressProperties["TcpPort"].Value = "1433" } $Tcp.alter() # Service needs to be restarted # Restart service Restart-Service "MSSQL`$$instanceName" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/appveyor/tests.cfg0000644000000000000000000000122200000000000017507 0ustar00rootroot00000000000000# in order for the tests to work correctly, the value for server # SHOULD NOT appear in any freetds.conf file. # # Ideally, the test DB server would be a non-default instance which allows full # tests of the instance and port parameters. [DEFAULT] server = localhost # IP Address should be included as well as it allows us to test connections # to the database in different ways. ipaddress = 127.0.0.1 username = sa password = Password12! database = tempdb # this shows all options need to run all tests [AllTestsWillRun] server = mydbserver ipaddress = 192.168.1.1 username = foouser password = somepass database = testdb port = 1435 instance = testinst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/build.py0000644000000000000000000002003600000000000015474 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Download and build FreeTDS library and build pymssql. """ import argparse from distutils.util import get_platform import multiprocessing import os from pathlib import Path import platform import shutil from subprocess import check_call, check_output, STDOUT import sys import tarfile def run(cmd, cwd=None, env=None, shell=True): check_call(str(cmd), cwd=str(cwd) if cwd else cwd, env=env, shell=shell, stderr=STDOUT) def download(args): args.ws_dir.mkdir(parents=True, exist_ok=True) if args.freetds_version.lower() == 'latest': args.freetds_version ='patched' arcname = f"freetds-{args.freetds_version}.tar.gz" url = f"{args.freetds_url.strip('/')}/{arcname}" freetds_dest = args.ws_dir / arcname if freetds_dest.exists() and not args.force_download: print(f"{freetds_dest} already exists") else: print(f"downloading {url} to {freetds_dest}") run(f"curl -sSf --retry 10 --max-time 10 {url} -o {freetds_dest}") if platform.system() == 'Windows': url = f"{args.iconv_url.strip('/')}/v{args.iconv_version}" iconv_dest = args.ws_dir / "win-iconv.zip" if iconv_dest.exists() and not args.force_download: print(f"{iconv_dest} already exists") else: run(f"curl -sSf {url} -o {iconv_dest}") else: iconv_dest = None print("downloading DONE") return freetds_dest, iconv_dest def build(args, freetds_archive): tar = tarfile.open(freetds_archive) topdir = tar.next().name srcdir = args.ws_dir / f"{topdir}" if srcdir.exists(): shutil.rmtree(srcdir) tar.extractall(args.ws_dir) tar.close() if args.prefix is None: args.prefix = args.ws_dir / f"{topdir}-bin" blddir = args.ws_dir / f"{topdir}-build" if blddir.exists(): shutil.rmtree(blddir) blddir.mkdir(parents=True) configure = srcdir / "configure" configure = [ f'"{configure}"', f"--prefix={args.prefix}", "--enable-msdblib", f"--with-tdsver={args.with_tdsver}", #"--disable-apps", "--disable-server", "--disable-pool", "--disable-odbc", f"--with-openssl={args.with_openssl}", "--with-gnutls=no", ] if args.enable_krb5: configure.append("--enable-krb5") if args.static_freetds: configure.extend(["--enable-static", "--disable-shared"]) else: configure.extend(["--enable-shared", "--disable-static"]) cmd = ' '.join(configure) env = os.environ.copy() env.update(CFLAGS="-fPIC") run(cmd, cwd=blddir, env=env) make = f"make -j {multiprocessing.cpu_count()}" run(make, cwd=blddir, env=env) make = "make install" run(make, cwd=blddir, env=env) def find_vcvarsall_env(): from distutils import _msvccompiler as _msvcc plat_name = get_platform() plat_spec = _msvcc.PLAT_TO_VCVARS[plat_name] vcvarsall, _ = _msvcc._find_vcvarsall(plat_spec) cmd = f'(call "{vcvarsall}" {plat_spec}>nul)&&"{sys.executable}" -c "import os;print(repr(os.environ))"' env = check_output(cmd, shell=True) env = eval(env.decode('ascii').strip('environ')) return env def build_windows(args, freetds_archive, iconv_archive): from zipfile import ZipFile wiconv = args.ws_dir / "win-iconv" if wiconv.exists(): shutil.rmtree(wiconv) wiconv.mkdir(parents=True) print(f"extracting {iconv_archive.name} -> {wiconv}") with ZipFile(iconv_archive) as zipf: for m in zipf.namelist(): fn = m.split('/', 1)[1] if fn: (wiconv / fn).write_bytes(zipf.read(m)) env = find_vcvarsall_env() cmd = f'"{args.cmake}" -G "NMake Makefiles" ' \ '-DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC=on -DBUILD_SHARED=off ' \ '-DBUILD_EXECUTABLE=off -DBUILD_TEST=off ' \ '.' print(f"running cmake in {wiconv}") run(cmd, cwd=wiconv, env=env) print(f"running nmake in {wiconv}") run("nmake", cwd=wiconv, env=env) tar = tarfile.open(freetds_archive) topdir = tar.next().name srcdir = args.ws_dir / f"{topdir}" if srcdir.exists(): shutil.rmtree(srcdir) tar.extractall(args.ws_dir) tar.close() if args.prefix is None: args.prefix = args.ws_dir / f"{topdir}-bin" blddir = args.ws_dir / f"{topdir}-build" if blddir.exists(): shutil.rmtree(blddir) blddir.mkdir(parents=True) os.makedirs(blddir / "lib", exist_ok=True) shutil.copy(wiconv / "iconv.h", blddir) shutil.copy(wiconv / "iconv.lib", blddir / "lib") krb5 = "ON" if args.enable_krb5 else "OFF" cmd = f'"{args.cmake}" -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release " \ "-DWITH_OPENSSL=ON -DENABLE_KRB5={krb5} -DCMAKE_INSTALL_PREFIX="{args.prefix}" "{srcdir}"' env["PATH"] += f";{args.msys}" run(cmd, cwd=blddir, env=env) run("nmake install", cwd=blddir, env=env) def parse_args(argv): parser = argparse.ArgumentParser(description=(__doc__)) a = parser.add_argument a('-f', '--force-download', action='store_true', help="force archive download") a('-u', '--freetds-url', default="http://ftp.freetds.org/pub/freetds/stable", help="URL to download FreeTDS archive") a('-v', '--freetds-version', default='latest', help="FreeTDS version to build") if platform.system() != 'Windows': a('-t', '--with-tdsver', choices=['5.0', '7.1', '7.2', '7.3', '7.4', 'auto'], default='auto', help="TDS protocol version") a('-o', '--with-openssl', choices=['yes', 'no'], default='yes', help="Build FreeTDS with or without OpenSSL support, default is 'yes'") a('-S', '--static-freetds', action='store_true', help="build FreeTDS staticly") a('-k', '--enable-krb5', dest="enable_krb5", action='store_true', help="enable krb5 support") base = Path('~/freetds').expanduser() a('-w', '--ws-dir', default=base, type=Path, help="workspace directory for building FreeTDS") a('-p', '--prefix', default=None, type=lambda x: Path(x) if x else None, help="prefix for installing FreeTDS, default is WS_DIR/prefix") a('-d', '--dist-dir', default=Path('./dist'), type=Path, help="where to put pymssql wheel, default is './dist'") a('-s', '--sdist', action='store_true', help="build sdist archive") if platform.system() == 'Windows': a('-U', '--iconv-url', default="https://codeload.github.com/win-iconv/win-iconv/zip", help="URL to download win-iconv.zip archive for build on Windows") a('-V', '--iconv-version', default='0.0.8', help="FreeTDS version to build") a('-m', '--msys', type=Path, default=Path("c:/tools/msys64/usr/bin"), help="Msys binaries installation directory") a('--cmake', type=Path, default=Path("C:/Program Files/CMake/bin/cmake.exe"), help="cmake executable for building FreeTDS") args = parser.parse_args(argv) return args def main(argv): args = parse_args(argv) args.ws_dir = args.ws_dir.absolute() if args.prefix is not None: args.prefix = args.prefix.absolute() freetds_archive, iconv_archive = download(args) if platform.system() == 'Windows': os.environ["PATH"] += f";{args.msys}" build_windows(args, freetds_archive, iconv_archive) else: build(args, freetds_archive) args.dist_dir = args.dist_dir.absolute() env = os.environ.copy() env.update(PYMSSQL_FREETDS=f"{args.prefix}") run(f"{sys.executable} -m pip wheel . -w {args.dist_dir}", shell=True, env=env) if args.sdist: fmt = 'zip' if platform.system() == 'Windows' else 'gztar' run(f"{sys.executable} setup.py sdist --formats={fmt} -d {args.dist_dir}", shell=True, env=env) if __name__ == '__main__': try: main(sys.argv[1:]) except Exception as exc: print(f"Build failed: {exc}") sys.exit(1) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/build_freetds.sh0000755000000000000000000000215000000000000017172 0ustar00rootroot00000000000000#!/bin/sh # # This script builds FreeTDS using MinGW to produce usable binaries for # building pymssql on Windows. # FREETDS="ftp://ftp.ibiblio.org/pub/Linux/ALPHA/freetds" CURRENT="$FREETDS/current/freetds-current.tgz" STABLE="$FREETDS/stable/freetds-stable.tgz" TEMPDIR=`mktemp -d` OLD_DIR=`pwd` # Retrieve the sources and extract them into a temporary directory wget $CURRENT -O $TEMPDIR/freetds-current.tgz tar zxf $TEMPDIR/freetds-current.tgz -C $TEMPDIR rm $TEMPDIR/freetds-current.tgz cd $TEMPDIR/freetds-* # Setup the build environment mkdir -p build/win32 cd build/win32 # Configure ../../configure --prefix=/usr/i586-mingw32msvc --host=i586-mingw32msvc \ --disable-rpath \ --disable-dependency-tracking \ --disable-shared \ --enable-static \ --enable-iconv \ --with-tdsver=7.1 \ --enable-sspi # Build make # Install mkdir ../pkg make install DESTDIR=`readlink -e ../pkg` # Package cd ../pkg/usr/i586-mingw32msvc mkdir freetds mv bin etc include lib freetds zip -r freetds.zip freetds mv freetds.zip $OLD_DIR/ # Cleanup rm -rf $TEMPDIR ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/build_manylinux_wheels.sh0000755000000000000000000000651400000000000021141 0ustar00rootroot00000000000000#!/bin/bash # Need to build linux wheel using manylinux # # https://github.com/pypa/manylinux # https://github.com/pypa/python-manylinux-demo # https://www.python.org/dev/peps/pep-0513/ # # You may need to make the build script executable # chmod +x /dev/build_wheels.sh # # Standard manylinux docker containers provided by pypa. For more information see the links above. # docker pull quay.io/pypa/manylinux1_x86_64 # docker pull quay.io/pypa/manylinux1_i686 # # The next set of instructions will let you run the container interactively # Provide a container name so it is easier to reference later # sudo docker run --name manylinux_x86_x64 -it -d --rm -w=/io -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 # docker ps # # Use the docker exec command to interact with our container # docker exec -it manylinux_x86_x64 ls # docker exec -it manylinux_x86_x64 ./io/dev/build_wheels.sh # # Stop the conatiner when done # docker stop manylinux_x86_x64 # # These docker commands will run, build the wheel, attempt to install and then finally upload # docker run --name manylinux_x86_x64 --rm -w=/io -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/dev/build_wheels.sh # docker run --name manylinux_i686 --rm -w=/io -v `pwd`:/io quay.io/pypa/manylinux1_i686 /io/dev/build_wheels.sh # # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html set -e -x if ! git status ; then git config --global --add safe.directory "$(pwd)" fi git status if which yum; then yum install -y openssl-devel krb5-devel else apt-get update apt-get install -y libssl1.1 apt-get install -y libssl-dev libkrb5-dev fi /opt/python/cp38-cp38/bin/python dev/build.py \ --ws-dir=./freetds \ --dist-dir=. \ --prefix=/usr/local \ --freetds-version="1.4.9" \ --with-openssl=yes \ --enable-krb5 \ --static-freetds # Install Python dependencies and compile wheels PYTHONS="cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312" for i in $PYTHONS; do PYBIN="/opt/python/$i/bin" if [ -d ${PYBIN} ] ; then "${PYBIN}/pip" install --upgrade pip setuptools Cython wheel "${PYBIN}/pip" wheel . -w . fi done # Verify the wheels and move from *-linux_* to -manylinux_* for wheel in ./*.whl; do if ! auditwheel show "$wheel"; then echo "Skipping non-platform wheel $wheel" else auditwheel repair "$wheel" -w ./dist fi done # Create .tar.gz dist. /opt/python/cp38-cp38/bin/python setup.py sdist # Install the wheels that were built. Need to be able to connect to mssql and to run the pytest suite after install # psutil 5.9.2 had a bug preventing it from being imported on some platforms. # https://github.com/giampaolo/psutil/issues/2138 for i in $PYTHONS; do PYBIN="/opt/python/$i/bin" if [ -d ${PYBIN} ] ; then "${PYBIN}/pip" install pymssql --no-index -f dist "${PYBIN}/pip" install 'psutil<5.9.2' pytest pytest-timeout if [ "$MANYLINUX" != "manylinux1" ] ; then "${PYBIN}/pip" install SQLAlchemy fi "${PYBIN}/pytest" -s . "${PYBIN}/python" -c "import pymssql; print(pymssql.version_info());" fi done # Remove wheel and egg directory for next container build (i686 vs x86_x64) rm -rf .eggs/ pymssql.egg-info/ # Cleanup FreeTDS directories rm -rf freetds/ # misc/ include/ doc/ samples/ vms/ wins32/ echo "Done building wheels." ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/ccompiler.py0000644000000000000000000000474500000000000016363 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- import contextlib from distutils import ccompiler from distutils.errors import * import os import sys import types def _has_function(self, funcname, includes=None, include_dirs=None, libraries=None, library_dirs=None): """Return a boolean indicating whether funcname is supported on the current platform. The optional arguments can be used to augment the compilation environment. """ # this can't be included at module scope because it tries to # import math which might not be available at that point - maybe # the necessary logic should just be inlined? import tempfile if includes is None: includes = [] if include_dirs is None: include_dirs = [] if libraries is None: libraries = [] if library_dirs is None: library_dirs = [] fd, fname = tempfile.mkstemp(".c", funcname, text=True) f = os.fdopen(fd, "w") try: for incl in includes: f.write("""#include "%s"\n""" % incl) f.write("""\ int main (int argc, char **argv) { %s; return 0; } """ % funcname) finally: f.close() try: objects = self.compile([fname], include_dirs=include_dirs) except CompileError: return False finally: os.remove(fname) try: self.link_executable(objects, "a.out", libraries=libraries, library_dirs=library_dirs) except (LinkError, TypeError): return False else: os.remove("a.out") finally: for fn in objects: os.remove(fn) return True @contextlib.contextmanager def stdchannel_redirected(stdchannel, dest_filename): """ A context manager to temporarily redirect stdout or stderr e.g.: with stdchannel_redirected(sys.stderr, os.devnull): ... """ try: oldstdchannel = os.dup(stdchannel.fileno()) dest_file = open(dest_filename, 'w') os.dup2(dest_file.fileno(), stdchannel.fileno()) yield finally: if oldstdchannel is not None: os.dup2(oldstdchannel, stdchannel.fileno()) if dest_file is not None: dest_file.close() def has_function(*args, **kw): with stdchannel_redirected(sys.stderr, os.devnull): return _has_function(*args, **kw) def new_compiler(): compiler = ccompiler.new_compiler() compiler.has_function = types.MethodType(has_function, compiler) return compiler ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/dev-requirements.pip0000644000000000000000000000006500000000000020034 0ustar00rootroot00000000000000Cython setuptools_git pytest==3.2.5 SQLAlchemy wheel ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/hudson.sh0000755000000000000000000000376100000000000015670 0ustar00rootroot00000000000000cd pymssql # Check the Python version case $PYTHON_VERSION in 27) PYTHON=python2.7 ;; 31) PYTHON=python3.1 echo "Python 3.1 not supported yet" exit 0 ;; *) echo "Unknown version of Python" exit 1 ;; esac # Check the Platform if [ $(expr match "$PLATFORM" 'win') -gt 0 ]; then if [ "$PLATFORM" = "win32" ]; then export WINEPREFIX="$HOME/.wine32" export WINEARCH="win32" elif [ "$PLATFORM" = "win64" ]; then export WINEPREFIX="$HOME/.wine64" unset WINEARCH else echo "Unknown version of Windows" exit 1 fi PYDIR="c:\Python$PYTHON_VERSION" PYTHON="$PYDIR\python.exe" SCRIPTS="$PYDIR\Scripts" # Do a clean to start out wine $PYTHON setup.py clean # Remove possibly left over directories rm -rf build dist # Build pymssql and create binaries wine $PYTHON setup.py build -c mingw32 bdist --formats=egg,wininst # Copy the binaries over to the downloads directory cp dist/pymssql-*.egg /srv/http/nginx/downloads/pymssql/snapshots/ cp dist/pymssql-*.exe /srv/http/nginx/downloads/pymssql/snapshots/ elif [ "$PLATFORM" = "linux" ]; then # Create the virtual python environment PYTHON_ENV=$(mktemp -d) if [ "$PYTHON" = "python3.1" ]; then VIRTUAL_ENV="python /usr/lib/python3.1/site-packages/virtualenv3.py" else VIRTUAL_ENV="virtualenv --distribute" fi $VIRTUAL_ENV --python=$PYTHON $PYTHON_ENV # Activate the virtual environment source $PYTHON_ENV/bin/activate # Install Cython easy_install Cython # Clean up the workspace python setup.py clean rm -rf build dist # Test building pymssql python setup.py build -c unix # Create a pymssql egg python setup.py bdist_egg # Copy the egg over to the downloads directory cp dist/pymssql-*.egg /srv/http/nginx/downloads/pymssql/snapshots/ # Remove the vitualenv rm -rf $VIRTUAL_ENV fi; ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/memmonitor.py0000755000000000000000000000163000000000000016565 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- #!/usr/bin/python """ Print memory usage for pid. """ import sys from subprocess import Popen, PIPE def fsize(fsize_b): """ Formats the bytes value into a string with KiB, MiB or GiB units :param fsize_b: the filesize in bytes :type fsize_b: int :returns: formatted string in KiB, MiB or GiB units :rtype: string **Usage** >>> fsize(112245) '109.6 KiB' """ fsize_kb = fsize_b / 1024.0 if fsize_kb < 1024: return "%.1f KiB" % fsize_kb fsize_mb = fsize_kb / 1024.0 if fsize_mb < 1024: return "%.1f MiB" % fsize_mb fsize_gb = fsize_mb / 1024.0 return "%.1f GiB" % fsize_gb pid = sys.argv[1] p = Popen(['ps', 'ufp', pid], stdout=PIPE, stdin=PIPE) p.wait() (vss, rss) = map(int, p.stdout.read().splitlines()[-1].split()[4:6]) print('VSS: %-8s (%s)' % (fsize(vss), vss)) print('RSS: %-8s (%s)' % (fsize(rss), rss)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/memtest.py0000755000000000000000000000072000000000000016054 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- #!/usr/bin/python """ """ from pymssql import _mssql test_str = 'testing' * 1000 for i in xrange(0, 1000): cnx = _mssql.connect('ukplcdbtest01', 'sa', 'deter101', charset='cp1252') cnx.select_db('tempdb') proc = cnx.init_procedure('pymssqlTestVarchar') proc.bind(test_str, _mssql.SQLVARCHAR, '@ivarchar') proc.bind(None, _mssql.SQLVARCHAR, '@ovarchar', output=True) return_value = proc.execute() cnx.close() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/memtest.sh0000755000000000000000000000011100000000000016030 0ustar00rootroot00000000000000python memtest.py & PID=$! watch -n 1 ./memmonitor.py $PID kill -15 $PID ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/requirements-dev.txt0000644000000000000000000000023300000000000020060 0ustar00rootroot00000000000000cython>=0.29.22 gevent psutil<5.9.5 pytest pytest-timeout setuptools>=54.0 setuptools_scm[toml]>=5.0,<7.0 sphinx-rtd-theme sphinx sqlalchemy wheel>=0.36.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/dev/test_manylinux_wheels.sh0000755000000000000000000000243100000000000021013 0ustar00rootroot00000000000000#!/bin/bash # Need to test the wheels that where build with build_manylinux_wheels.sh # Rename test template file for CI mv /io/tests/tests.cfg.tpl /io/tests/tests.cfg # Setup xml results folder for CI if it does not exist if [ ! -d /io/results ]; then mkdir /io/results fi # Install Python dependencies and compile wheels for PYBIN in /opt/python/*/bin; do "${PYBIN}/pip" install pytest pytest-timeout SQLAlchemy Sphinx sphinx-rtd-theme Cython wheel done # Install the wheels that were built. Need to be able to connect to mssql and to run the pytest suite after install for PYBIN in /opt/python/*/bin/; do "${PYBIN}/pip" install pymssql --no-index -f /io/dist "${PYBIN}/python" -c "import pymssql; pymssql.__version__;" export TEST_PY="$(${PYBIN}/python -c 'import platform; major, minor, patch = platform.python_version_tuple(); print(major+minor+patch)')" "${PYBIN}pytest" /io --junitxml=/io/results/${TEST_PY}_test_results.xml done ## Run SQL Alchemy Tests #pushd /io #for PYBIN in /opt/python/*/bin/; do # PYV=`python -c "import sys;t='{v[0]}'.format(v=list(sys.version_info[:1]));sys.stdout.write(t)";` # if [ $PYV == '2' ]; then # pip install mock # fi # "${PYBIN}/pip" install nose # "${PYBIN}python" tests/run_sqlalchemy_tests.py #done #popd ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docker-compose.yml0000644000000000000000000000145300000000000016704 0ustar00rootroot00000000000000version: '2' services: sqlserver: image: microsoft/mssql-server-linux environment: - ACCEPT_EULA=Y - SA_PASSWORD=YourStrong!Passw0rd ports: - 1433:1433 logging: driver: "none" networks: vpcbr: ipv4_address: 10.5.0.5 pymssql_x86_x64: image: quay.io/pypa/manylinux1_x86_64 depends_on: - sqlserver volumes: - .:/io command: tail -f /dev/null networks: vpcbr: ipv4_address: 10.5.0.6 pymssql_i686: image: quay.io/pypa/manylinux1_i686 depends_on: - sqlserver volumes: - .:/io command: tail -f /dev/null networks: vpcbr: ipv4_address: 10.5.0.7 networks: vpcbr: driver: bridge ipam: config: - subnet: 10.5.0.0/16 gateway: 10.5.0.1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5924108 pymssql-2.2.11/docs/0000755000000000000000000000000000000000000014174 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/Makefile0000644000000000000000000001515600000000000015644 0ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pymssql.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pymssql.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pymssql" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pymssql" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/_mssql_examples.rst0000644000000000000000000001153000000000000020122 0ustar00rootroot00000000000000=================== ``_mssql`` examples =================== Example scripts using ``_mssql`` module. Quickstart usage of various features ==================================== :: from pymssql import _mssql conn = _mssql.connect(server='SQL01', user='user', password='password', \ database='mydatabase') conn.execute_non_query('CREATE TABLE persons(id INT, name VARCHAR(100))') conn.execute_non_query("INSERT INTO persons VALUES(1, 'John Doe')") conn.execute_non_query("INSERT INTO persons VALUES(2, 'Jane Doe')") :: # how to fetch rows from a table conn.execute_query('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in conn: print "ID=%d, Name=%s" % (row['id'], row['name']) .. versionadded:: 2.1.0 Iterating over query results by iterating over the connection object just like it's already possible with ``pymssql`` connections is new in 2.1.0. :: # examples of other query functions numemployees = conn.execute_scalar("SELECT COUNT(*) FROM employees") numemployees = conn.execute_scalar("SELECT COUNT(*) FROM employees WHERE name LIKE 'J%'") # note that '%' is not a special character here employeedata = conn.execute_row("SELECT * FROM employees WHERE id=%d", 13) :: # how to fetch rows from a stored procedure conn.execute_query('sp_spaceused') # sp_spaceused without arguments returns 2 result sets res1 = [ row for row in conn ] # 1st result res2 = [ row for row in conn ] # 2nd result :: # how to get an output parameter from a stored procedure sqlcmd = """ DECLARE @res INT EXEC usp_mystoredproc @res OUT SELECT @res """ res = conn.execute_scalar(sqlcmd) :: # how to get more output parameters from a stored procedure sqlcmd = """ DECLARE @res1 INT, @res2 TEXT, @res3 DATETIME EXEC usp_getEmpData %d, %s, @res1 OUT, @res2 OUT, @res3 OUT SELECT @res1, @res2, @res3 """ res = conn.execute_row(sqlcmd, (13, 'John Doe')) :: # examples of queries with parameters conn.execute_query('SELECT * FROM empl WHERE id=%d', 13) conn.execute_query('SELECT * FROM empl WHERE name=%s', 'John Doe') conn.execute_query('SELECT * FROM empl WHERE id IN %s', ((5, 6),)) conn.execute_query('SELECT * FROM empl WHERE name LIKE %s', 'J%') conn.execute_query('SELECT * FROM empl WHERE name=%(name)s AND city=%(city)s', \ { 'name': 'John Doe', 'city': 'Nowhere' } ) conn.execute_query('SELECT * FROM cust WHERE salesrep=%s AND id IN %s', \ ('John Doe', (1, 2, 3))) conn.execute_query('SELECT * FROM empl WHERE id IN %s', (tuple(xrange(4)),)) conn.execute_query('SELECT * FROM empl WHERE id IN %s', \ (tuple([3, 5, 7, 11]),)) :: conn.close() Please note the usage of iterators and ability to access results by column name. Also please note that parameters to connect method have different names than in ``pymssql`` module. An example of exception handling ================================ .. code-block:: python from pymssql import _mssql conn = _mssql.connect(server='SQL01', user='user', password='password', database='mydatabase') try: conn.execute_non_query('CREATE TABLE t1(id INT, name VARCHAR(50))') except _mssql.MssqlDatabaseException as e: if e.number == 2714 and e.severity == 16: # table already existed, so quieten the error else: raise # re-raise real error finally: conn.close() Custom message handlers ======================= .. versionadded:: 2.1.1 You can provide your own message handler callback function that will be invoked by the stack with informative messages sent by the server. Set it on a per ``_mssql`` :class:`connection <_mssql.MSSQLConnection>` basis by using the :meth:`_mssql.MSSQLConnection.set_msghandler` method: .. code-block:: python from pymssql import _mssql def my_msg_handler(msgstate, severity, srvname, procname, line, msgtext): """ Our custom handler -- It simpy prints a string to stdout assembled from the pieces of information sent by the server. """ print("my_msg_handler: msgstate = %d, severity = %d, procname = '%s', " "line = %d, msgtext = '%s'" % (msgstate, severity, procname, line, msgtext)) cnx = _mssql.connect(server='SQL01', user='user', password='password') try: cnx.set_msghandler(my_msg_handler) # Install our custom handler cnx.execute_non_query("USE mydatabase") # It gets called at this point finally: cnx.close() Something similar to this would be printed to the standard output:: my_msg_handler: msgstate = x, severity = y, procname = '', line = 1, msgtext = 'Changed database context to 'mydatabase'.' .. todo:: Add an example of invoking a Stored Procedure using ``_mssql``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/azure.rst0000644000000000000000000000176200000000000016062 0ustar00rootroot00000000000000================================ Connecting to Azure SQL Database ================================ Starting with version 2.1.1 pymssql can be used to connect to *Microsoft Azure SQL Database*. Make sure the following requirements are met: * Use FreeTDS 0.91 or newer * Use TDS protocol 7.1 or newer * Make sure FreeTDS is built with SSL support * Specify the database name you are connecting to in the *database* parameter of the relevant ``connect()`` call * **IMPORTANT**: Do not use ``username@server.database.windows.net`` for the *user* parameter of the relevant ``connect()`` call! You must use the shorter ``username@server`` form instead! Example:: pymssql.connect("xxx.database.windows.net", "username@xxx", "password", "db_name") or, if you've defined ``myalias`` in the ``freetds.conf`` FreeTDS config file:: [myalias] host = xxx.database.windows.net tds version = 7.1 ... then you could use:: pymssql.connect("myalias", "username@xxx", "password", "db_name") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/building_and_developing.rst0000644000000000000000000001376200000000000021572 0ustar00rootroot00000000000000=============================== Building and developing pymssql =============================== Required software _________________ To build ``pymssql`` you should have: * `Python `_ >= 3.6 including development files. Please research your OS usual software distribution channels, e.g, ``python-dev`` or ``python-devel`` packages on Linux. * `Cython `_ - to compile ``pymssql`` source files to ``C``. * `setuptools `_ - for ``setup.py`` support. * `setuptools_scm `_ - for extracting version information from ``git``. * `wheel `_ - for building python wheels. * `FreeTDS `_ >= 1.2 including development files. Please research your OS usual software distribution channels, e.g, ``freetds-dev`` or ``freetds-devel`` packages on Linux. * `GNU gperf `_ - a perfect hash function generator, needed for FreeTDS. On Windows prebuild version is available from `Chocolatey `_. * `win-iconv `_ (Windows only) - developing ``pymssql`` on Windows also requires this library to build FreeTDS. * `OpenSSL `_ - If you need to connect to Azure make sure FreeTDS is built with SSL support. Please research your OS usual software distribution channels. On Windows one easy way is to get prebuild libraries from `Chocolatey `_. For testing the follwing is required: * Microsoft SQL Server. One possibility is to use official docker images for Microsoft SQL Server on Linux available `here `_. * `pytest `_ - to run the tests. * `pytest-timeout `_ - for limiting long running tests. * `psutil `_ - for memory monitoring. * `gevent `_ (optional) - for async tests. * `Sqlalchemy `_ - (optional) - for basic Sqlalchemy testing. To build documentation `Sphinx `_ and `sphinx-rtd-theme `_ are also needed. Windows _______ In addition to the requirements above when developing ``pymssql`` on the Windows platform you will need these additional tools installed: * Visual Studio C++ Compiler Tools, see `Python documentation `_ for instructions on what components to install. * `Cmake `_ for building FreeTDS and win-iconv. * `curl `_ - for downloading FreeTDS and win-iconv. .. note:: If Windows computer is not readily available then `virtual machine `_ from Microsoft could be used. Linux _____ For example on Alpine Linux the following command will install all necessary packages for building pymssql:: apk add git gcc musl-dev krb5-dev openssl-dev freetds-dev Building ``pymssql`` wheel __________________________ It is recommended to use python virtual environment for building ``pymssql``:: python3 -m venv if using ``bash``:: source /bin/activate or if on Windows:: /scripts/activate.bat then install required python packages:: pip intall -U pip pip install dev/requirements-dev.txt If and now build wheel:: python3 setup.py bdist_wheel or:: pip wheel . Environment Variables _____________________ By default ``setup.py`` links against OpenSSL if it is available, links FreeTDS statically and looks for FreeTDS headers and libraries in places standard for the OS, but there are several environment variables for build customization: LINK_FREETDS_STATICALLY = [YES|NO|1|0|TRUE|FALSE] default - YES, defines if FreeTDS is linked statically or not. LINK_OPENSSL = [YES|NO|1|0|TRUE|FALSE] default - YES, defines if ``pymssql`` is linked against OpenSSL. PYMSSQL_FREETDS if defined, determines prefix of the FreeTDS installation. PYMSSQL_FREETDS_INCLUDEDIR if defined, alows to fine tune where to search for FreeTDS headers. PYMSSQL_FREETDS_LIBDIR if defined, alows to fine tune where to search for FreeTDS libraries. Example: .. code-block:: console PYMSSQL_FREETDS=/tmp/freetds python3 setup.py bdist_wheel Building FreeTDS and ``pymssql`` from scratch _____________________________________________ If one wants to use some specific FreeTDS version then there is a script ``dev/build.py`` that downloads and builds required FreeTDS version sources (and win-conv on Windows) and builds ``pymssql`` wheel. Run:: python dev/build.py --help for supported options. Testing _______ .. danger:: ALL DATA IN TESTING DBS WILL BE DELETED !!!! You will need to install two additional packages for testing:: easy_install pytest SQLAlchemy You should build the package with:: python setup.py develop You need to setup a ``tests.cfg`` file in ``tests/`` with the correct DB connection information for your environment:: cp tests/tests.cfg.tpl tests/tests.cfg vim|emacs|notepad tests/tests.cfg To run the tests:: cd tests # optional py.test Which will go through and run all the tests with the settings from the ``DEFAULT`` section of ``tests.cfg``. To run with a different ``tests.cfg`` section:: py.test --pymssql-section= example:: py.test --pymssql-section=AllTestsWillRun to avoid slow tests:: py.test -m "not slow" or:: py.test --skip-slow to skip tests that require MSSQL server:: py.test --skip-mssql-server-required to select specific tests to run:: py.test tests/test_types.py py.test tests/test_types.py tests/test_sprocs.py py.test tests/test_types.py::TestTypes py.test tests/test_types.py::TestTypes::test_image ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/changelog.rst0000644000000000000000000000024000000000000016651 0ustar00rootroot00000000000000========== Change log ========== .. include:: ../ChangeLog.rst .. Please see the `ChangeLog.rst` file located in the root of the pymssql source code tree. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/conf.py0000644000000000000000000002210700000000000015475 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # pymssql documentation build configuration file, created by # sphinx-quickstart on Sat Dec 28 22:08:07 2013. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os from setuptools_scm import get_version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) def extract_version(): version = get_version(root='..', relative_to=__file__) return version # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx_rtd_theme', 'sphinx_copybutton', 'sphinx_toggleprompt', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'pymssql' copyright = '2001-2021, pymssql developers' # uncomment this if you don't want to verify at all tls_verify = False # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = extract_version() # The short X.Y version. version = '.'.join(release.split('.')[:3]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build', 'history.rst'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False intersphinx_mapping = { 'python': ('http://docs.python.org/3', None), 'gevent': ('http://www.gevent.org/', None), } # Python's docs don't change every week. intersphinx_cache_limit = 90 # days # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme = 'default' # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_context = { 'display_github': True, 'github_user': 'pymssql', 'github_repo': 'pymssql', } # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pymssqldoc' todo_include_todos = True # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'pymssql.tex', 'pymssql Documentation', 'pymssql developers', 'manual', True), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pymssql', 'pymssql Documentation', ['pymssql developers'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'pymssql', 'pymssql Documentation', 'pymssql developers', 'pymssql', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/docker.rst0000644000000000000000000000327600000000000016205 0ustar00rootroot00000000000000====== Docker ====== (Experimental) There is a pymssql Docker image on the Docker Registry at: https://registry.hub.docker.com/u/pymssql/pymssql/ The image bundles: * Ubuntu 14.04 LTS (trusty) * Python 2.7.6 * pymssql 2.1.2.dev * FreeTDS 0.91 * SQLAlchemy 0.9.8 * Alembic 0.7.4 * Pandas 0.15.2 * Numpy 1.9.1 * IPython 2.3.1 To try it, first download the image (this requires Internet access and could take a while): .. code-block:: bash docker pull pymssql/pymssql Then run a Docker container using the image with: .. code-block:: bash docker run -it --rm pymssql/pymssql By default, if no command is specified, an `IPython `_ shell is invoked. You can override the command if you wish -- e.g.: .. code-block:: bash docker run -it --rm pymssql/pymssql bin/bash Here's how using the Docker container looks in practice: .. code-block:: bash $ docker pull pymssql/pymssql ... $ docker run -it --rm pymssql/pymssql Python 2.7.6 (default, Mar 22 2014, 22:59:56) Type "copyright", "credits" or "license" for more information. IPython 2.1.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: import pymssql; pymssql.__version__ Out[1]: u'2.1.1' In [2]: import sqlalchemy; sqlalchemy.__version__ Out[2]: '0.9.7' In [3]: import pandas; pandas.__version__ Out[3]: '0.14.1' See the Docker docs for installation instructions for a number of platforms; you can try this link: https://docs.docker.com/installation/#installation ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/faq.rst0000644000000000000000000002372400000000000015505 0ustar00rootroot00000000000000========================== Frequently asked questions ========================== Cannot connect to SQL Server ============================ If your Python program/script can't connect to a *SQL Server* instance, try the following: * By default *SQL Server* 2005 and newer don't accept remote connections, you have to use *SQL Server Surface Area Configuration* and/or *SQL Server Configuration Manager* to enable specific protocols and network adapters; don't forget to restart *SQL Server* after making these changes, * If *SQL Server* is on a remote machine, check whether connections are not blocked by any intermediate firewall device, firewall software, antivirus software, or other security facility, * Check that you can connect with another tool. If you are using `FreeTDS `_, then you can use the included ``tsql`` command to try to connect -- it looks like this:: $ tsql -H sqlserverhost -p 1433 -U user -P password -D tempdb locale is "en_US.UTF-8" locale charset is "UTF-8" using default charset "UTF-8" Setting tempdb as default database in login packet 1> SELECT @@VERSION 2> GO Microsoft SQL Server 2012 - 11.0.2100.60 (X64) Feb 10 2012 19:39:15 Copyright (c) Microsoft Corporation Developer Edition (64-bit) on Windows NT 6.1 (Build 7601: Service Pack 1) (1 row affected) .. note:: Note that I use the ``-H`` option rather than the ``-S`` option to ``tsql``. This is because with ``-H``, it will bypass reading settings from the ``freetds.conf`` file like ``port`` and ``tds version``, and so this is more similar to what happens with pymssql. If you **can't** connect with ``tsql`` or other tools, then the problem is probably not pymssql; you probably have a problem with your server configuration (see below), :doc:`FreeTDS Configuration `, network, etc. If you **can** connect with ``tsql``, then you should be able to connect with pymssql with something like this:: >>> import pymssql >>> conn = pymssql.connect( ... server="sqlserverhost", ... port=1433, ... user="user", ... password="password", ... database="tempdb") >>> conn >>> cursor = conn.cursor() >>> cursor.execute("SELECT @@VERSION") >>> print(cursor.fetchone()[0]) Microsoft SQL Server 2012 - 11.0.2100.60 (X64) Feb 10 2012 19:39:15 Copyright (c) Microsoft Corporation Developer Edition (64-bit) on Windows NT 6.1 (Build 7601: Service Pack 1) If something like the above doesn't work, then you can try to diagnose by setting one or both of the following `FreeTDS environment variables that control logging `_: * ``TDSDUMP`` * ``TDSDUMPCONFIG`` Either or both of these can be set. They can be set to a filename or to ``stdout`` or ``stderr``. These will cause FreeTDS to output a ton of information about what it's doing and you may very well spot that it's not using the port that you expected or something similar. For example:: >>> import os >>> os.environ['TDSDUMP'] = 'stdout' >>> >>> import pymssql >>> conn = pymssql.connect(server="sqlserverhost") log.c:194:Starting log file for FreeTDS 0.92.dev.20140102 on 2014-01-09 14:05:32 with debug flags 0x4fff. config.c:731:Setting 'dump_file' to 'stdout' from $TDSDUMP. ... dblib.c:7934:20013: "Unknown host machine name" dblib.c:7955:"Unknown host machine name", client returns 2 (INT_CANCEL) util.c:347:tdserror: client library returned TDS_INT_CANCEL(2) util.c:370:tdserror: returning TDS_INT_CANCEL(2) login.c:418:IP address pointer is empty login.c:420:Server sqlserverhost:1433 not found! ... .. note:: Note that pymssql will use a default port of 1433, despite any ports you may have specified in your ``freetds.conf`` file. So if you have SQL Server running on a port other than 1433, you must explicitly specify the ``port`` in your call to ``pymssql.connect``. You cannot rely on it to pick up the port in your ``freetds.conf``, even though ``tsql -S`` might do this. This is why I recommend using ``tsql -H`` instead for diagnosing connection problems. It is also useful to know that ``tsql -C`` will output a lot of information about FreeTDS, that can be useful for diagnosing problems:: $ tsql -C Compile-time settings (established with the "configure" script) Version: freetds v0.92.dev.20140102 freetds.conf directory: /usr/local/etc MS db-lib source compatibility: no Sybase binary compatibility: no Thread safety: yes iconv library: yes TDS version: 5.0 iODBC: yes unixodbc: no SSPI "trusted" logins: no Kerberos: no OpenSSL: no GnuTLS: no * If you use pymssql on Linux/Unix with FreeTDS, check that FreeTDS's configuration is ok and that it can be found by pymssql. The easiest way is to test connection using ``tsql`` utility which can be found in FreeTDS package. See :doc:`FreeTDS Configuration ` for more info, Returned dates are not correct ============================== If you use pymssql on Linux/\*nix and you suspect that returned dates are not correct, please read the :doc:`FreeTDS and dates ` page. pymssql does not unserialize ``DATE`` and ``TIME`` columns to ``datetime.date`` and ``datetime.time`` instances =============================================================================================================== You may notice that pymssql will unserialize a ``DATETIME`` column to a :class:`python:datetime.datetime` instance, but it will unserialize ``DATE`` and ``TIME`` columns as simple strings. For example:: >>> cursor.execute(""" ... CREATE TABLE dates_and_times ( ... datetime DATETIME, ... date DATE, ... time TIME, ... ) ... """) >>> cursor.execute("INSERT INTO dates_and_times VALUES (GETDATE(), '20140109', '6:17')") >>> cursor.execute("SELECT * FROM dates_and_times") >>> cursor.fetchall() [{u'date': u'2014-01-09', u'time': u'06:17:00.0000000', u'datetime': datetime.datetime(2014, 1, 9, 12, 41, 59, 403000)}] >>> cursor.execute("DROP TABLE dates_and_times") Yep, so the problem here is that ``DATETIME`` has been supported by `FreeTDS `_ for a long time, but ``DATE`` and ``TIME`` are newer types in SQL Server, Microsoft never added support for them to db-lib and FreeTDS added support for them in version 0.95. If you need support for these data types (i.e. they get returned from the database as their native corresponding Python data types instead of as strings) as well as for the ``DATETIME2`` one, then make sure the following conditions are met: * You are connecting to SQL Server 2008 or newer. * You are using FreeTDS 0.95 or newer. * You are using TDS protocol version 7.3 or newer. Shared object "libsybdb.so.3" not found ======================================= On Linux/\*nix you may encounter the following behaviour:: >>> from pymssql import _mssql Traceback (most recent call last): File "", line 1, in ? ImportError: Shared object "libsybdb.so.3" not found It may mean that the FreeTDS library is unavailable, or that the dynamic linker is unable to find it. Check that it is installed and that the path to ``libsybdb.so`` is in ``/etc/ld.so.conf`` file. Then do ``ldconfig`` as root to refresh linker database. On Solaris, I just set the ``LD_LIBRARY_PATH`` environment variable to the directory with the library just before launching Python. pymssql 2.x bundles the FreeTDS ``sybdb`` library for supported platforms. This error may show up in 2.x versions if you are trying to build with your own FreeTDS. "DB-Lib error message 20004, severity 9: Read from SQL server failed" error appears =================================================================================== On Linux/\*nix you may encounter the following behaviour:: >>> from pymssql import _mssql >>> c=_mssql.connect('hostname:portnumber','user','pass') Traceback (most recent call last): File "", line 1, in _mssql.DatabaseException: DB-Lib error message 20004, severity 9: Read from SQL server failed. DB-Lib error message 20014, severity 9: Login incorrect. It may happen when one of the following is true: * ``freetds.conf`` file cannot be found, * ``tds version`` in ``freetds.conf`` file is not ``7.0`` or ``4.2``, * any character set is specified in ``freetds.conf``, * an unrecognized character set is passed to :func:`_mssql.connect()` or :func:`pymssql.connect()` method. ``"Login incorrect"`` following this error is spurious, real ``"Login incorrect"`` messages has code=18456 and severity=14. Unable to use long username and password ======================================== This is a solved FreeTDS problem but you need to be using FreeTDS 0.95 or newer, if you are stuck with 0.91 then keep in mind this limitation, even when you can get usernames, passwords longer than 30 to work on tsql. More troubleshooting ==================== If the above hasn't covered the problem you can send a message describing it to the pymssql mailing list. You can also consult FreeTDS troubleshooting `page for issues related to the TDS protocol`_. .. _page for issues related to the TDS protocol: https://www.freetds.org/userguide/troubleshooting.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/freetds.rst0000644000000000000000000001356000000000000016367 0ustar00rootroot00000000000000======= FreeTDS ======= Installation ============ Linux ----- On Linux you can choose between (for the two former choices, when you start the the pymssql installation process it will look for and pick the header files and libraries for FreeTDS in some usual system-wide locations): * Use the FreeTDS installation provided by the packages/ports system. * `Build it and install yourself `_. * Use the bundled static FreeTDS libraries: .. code-block:: bash export PYMSSQL_BUILD_WITH_BUNDLED_FREETDS=1 pip install pymssql These static libraries are built on a x86_64 Ubuntu 14.04 system by using the following sequence: .. code-block:: bash export CFLAGS="-fPIC" # for the 64 bits version or .. code-block:: bash export CFLAGS="-m32 -fPIC" LDFLAGS="-m32" # for the 32 bits version and then: .. code-block:: bash ./configure --enable-msdblib \ --prefix=/usr --sysconfdir=/etc/freetds --with-tdsver=7.1 \ --disable-apps --disable-server --disable-pool --disable-odbc \ --with-openssl=no --with-gnutls=no make .. versionchanged:: 2.1.3 Version of FreeTDS Linux static libraries bundled with pymssql is `0.95.95`_. .. versionchanged:: 2.1.2 Version of FreeTDS Linux static libraries bundled with pymssql is `0.95.81`_ obtained from branch `Branch-0_95`_ of the official Git repository. Up to 2.1.1 the version of FreeTDS bundled was 0.91. .. _0.95.95: https://github.com/FreeTDS/freetds/tree/c9d284c767e569c9ae58ca0e2ad9dcd7c2cc9e55 .. _0.95.81: https://github.com/FreeTDS/freetds/tree/110179b9c83fe9af88d4c29658dca05e5295ecbb .. _Branch-0_95: https://github.com/FreeTDS/freetds/tree/Branch-0_95 Mac OS X (with `Homebrew `_) --------------------------------------------- .. code-block:: bash brew install freetds Windows ------- You can: #. Simply use our official Wheels which include FreeTDS statically linked and have no SSL support. #. Build pymssql yourself. In this case you have the following choices regarding FreeTDS: * Use binaries we maintain at https://github.com/ramiro/freetds/releases Choose the .zip file appropriate for your architecture (``x86`` vs. ``x86_64``) and your Python version (``vs2008`` for Python 2.7, ``vs2010`` for Python 3.3 and 3.4, ``vs2015`` for Python 3.5 and 3.6). Those builds include iconv support (via `win-iconv `_ statically linked). They provide both static and dynamic library versions of FreeTDS and versions built both with and without SSL support via OpenSSL (only dinamically linked). To install OpenSSL you'll need the distribution that can be downloaded from http://www.npcglib.org/~stathis/blog/precompiled-openssl/. Choose the right .7z file for your Python version (``vs2008`` for Python 2.7, ``vs2010`` for Python 3.3 and 3.4, ``vs2015`` for Python 3.5 and 3.6). * Or you can `build it yourself `_. .. versionchanged:: 2.1.3 FreeTDS is linked statically again on our official Windows binaries. pymssql version 2.1.2 included a change in the official Windows Wheels by which FreeTDS was dinamically linked. Read the relevant change log entry for the rationale behind that decision. Given the fact this didn't have a good reception from our users, this change has been undone in 2.1.3, FreeTDS is statically linked like it happened until version 2.1.1. Configuration ============= pymssql uses FreeTDS package to connect to SQL Server instances. You have to tell it how to find your database servers. The most basic info is host name, port number, and protocol version to use. The system-wide FreeTDS configuration file is ``/etc/freetds.conf`` or ``C:\freetds.conf``, depending upon your system. It is also possible to use a user specific configuration file, which is ``$HOME/.freetds.conf`` on Linux and ``%APPDATA%\.freetds.conf`` on Windows. Suggested contents to start with is at least:: [global] port = 1433 tds version = 7.0 With this config you will be able to enter just the hostname to :func:`pymssql.connect()` and :func:`_mssql.connect()`:: import pymssql connection = pymssql.connect(server='mydbserver', ...) Otherwise you will have to enter the portname as in:: connection = pymssql.connect(server='mydbserver:1433', ...) To connect to instance other than the default, you have to know either the instance name or port number on which the instance listens:: connection = pymssql.connect(server='mydbserver\\myinstancename', ...) # or by port number (suppose you confirmed that this instance is on port 1237) connection = pymssql.connect(server='mydbserver:1237', ...) Please see also the :doc:`pymssql module reference `, :doc:`_mssql module reference `, and :doc:`FAQ ` pages. For more information on configuring FreeTDS please go to https://www.freetds.org/userguide/ Testing the connection ---------------------- If you're sure that your server is reachable, but pymssql for some reason don't let you connect, you can check the connection with ``tsql`` utility which is part of FreeTDS package:: $ tsql Usage: tsql [-S | -H -p ] -U [-P ] [-I ] [-o ] [-t delim] [-r delim] [-D database] (...) $ tsql -S mydbserver -U user .. note:: Use the above form if and only if you specified server alias for mydbserver in freetds.conf. Otherwise use the host/port notation:: $ tsql -H mydbserver -p 1433 -U user You'll be prompted for a password and if the connection succeeds, you'll see the SQL prompt:: 1> You can then enter queries and terminate the session with ``exit``. If the connection fails, ``tsql`` utility will display appropriate message. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/freetds_and_dates.rst0000644000000000000000000000424500000000000020371 0ustar00rootroot00000000000000================= FreeTDS and dates ================= Explanation of how pymssql and FreeTDS can break dates. Summary ======= Make sure that FreeTDS is compiled with ``--enable-msdblib`` ``configure`` option, or your queries will return wrong dates -- ``"2010-00-01"`` instead of ``"2010-01-01"``. Details ======= There's an obscure problem on Linux/\*nix that results in dates shifted back by 1 month. This behaviour is caused by different ``dbdatecrack()`` prototypes in *Sybase Open Client DB-Library/C* and the *Microsoft SQL DB Library for C*. The first one returns month as 0..11 whereas the second gives month as 1..12. See this `FreeTDS mailing list post`_, `Microsoft manual for dbdatecrack()`_, and `Sybase manual for dbdatecrack()`_ for details. FreeTDS, which is used on Linux/\*nix to connect to *Sybase* and *MS SQL* servers, tries to imitate both modes: * Default behaviour, when compiled without ``--enable-msdblib``, gives ``dbdatecrack()`` which is Sybase-compatible, * When configured with ``--enable-msdblib``, the ``dbdatecrack()`` function is compatible with *MS SQL* specs. pymssql requires *MS SQL* mode, evidently. Unfortunately at runtime we can't reliably detect which mode FreeTDS was compiled in (as of FreeTDS 0.63). Thus at runtime it may turn out that dates are not correct. If there was a way to detect the setting, pymssql would be able to correct dates on the fly. If you can do nothing about FreeTDS, there's a workaround. You can redesign your queries to return string instead of bare date: .. code-block:: sql SELECT datecolumn FROM tablename can be rewritten into: .. code-block:: sql SELECT CONVERT(CHAR(10),datecolumn,120) AS datecolumn FROM tablename This way SQL will send you string representing the date instead of binary date in datetime or smalldatetime format, which has to be processed by FreeTDS and pymssql. .. _FreeTDS mailing list post: http://lists.ibiblio.org/pipermail/freetds/2002q3/008336.html .. _Microsoft manual for dbdatecrack(): http://msdn.microsoft.com/en-us/library/aa937027(SQL.80).aspx .. _Sybase manual for dbdatecrack(): http://manuals.sybase.com/onlinebooks/group-cnarc/cng1110e/dblib/@Generic__BookTextView/15108 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/history.rst0000644000000000000000000000331200000000000016426 0ustar00rootroot00000000000000=============== Project history =============== Evolution of pymssql. This page stores the most important dates for pymssql project before it moved to Google Code. The beginning ============= The project is registered on SourceForge on 2001-11-14 by 박준철 -- Park Joon-cheol. Important releases ================== * 2001-11-25 - version 0.0.1 for Windows by 박준철 * 2003-02-03 - version 0.2 by 박준철 * 2003-02-14 - version 0.3 by 박준철 * 2003-02-26 - version 0.4 by 박준철 * 2003-06-24 - version 0.5 by 박준철 * 2003-10-21 - version 0.5.1 by 박준철 * 2004-04-09 - version 0.5.2 by 박준철 2005-07 Andrzej Kukuła joins the project. * 2005-07-23 - version 0.7.1 by Andrzej Kukuła * 2005-09-05 - version 0.7.2 by Andrzej Kukuła * 2005-09-05 - version 0.7.3 by Andrzej Kukuła * 2006-02-23 - version 0.7.4 by Andrzej Kukuła License change ============== * 2006-05-03 - 박준철 in his message to SourceForge News announces: ,,We decide to change the license. Now, CVS Version is under the LGPL. This change of license from GPL to LGPL will be helpful for many F/OSS developers.'' LGPLed releases =============== * 2006-09-24 - version 0.8.0 by Andrzej Kukuła * 2009-01-29 - version 1.0.0 by Andrzej Kukuła * 2009-02-05 - version 1.0.1 by Andrzej Kukuła * 2009-04-28 - version 1.0.2 by Andrzej Kukuła 2009-05 Damien Churchill joins the project in effort to implement better support for stored procedures. However he decides to do much more than that, most important was to rewrite the whole thing in Cython, thus providing support for many more Python versions. * 2010-02-24 - pre-release 1.9.905 by Damien Churchill * 2010-02-26 - pre-release 1.9.905.1 by Damien Churchill ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5924108 pymssql-2.2.11/docs/images/0000755000000000000000000000000000000000000015441 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/images/pymssql-stack.graphml0000644000000000000000000003564300000000000021643 0ustar00rootroot00000000000000 FreeTDS Python application Python application db-lib ct-lib tdsodbc libtds pymssql project _mssql pymssql ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/images/pymssql-stack.png0000644000000000000000000006167700000000000021003 0ustar00rootroot00000000000000PNG  IHDRNcIDATx XUeߕZ9YP2c8vx-昿VS9dӨSf9cf3EA1x@D䬂Nr>(rk}^{^\Z>ϳu6 F0* F`T0* LgXg8m@wު~7dO$Ξ3|{i?S73%q\k>r\&q K$hrZSqɚԼ KMs1;qq\,p\&QqG8.Bq8.Lq8n&{5 =q9.l N~G;;;o {wl{W3H:qG8#$k?µGp퇺vv辺!W+t_ 쾺mWwێmۺ۶vtmn}ůʦ+ᄇOCw[Wuuew˿[nYmw˲-nYݼ͋niNwLp/{|mZW%`9ǕqE++"+ wN&40޽R{uDK%ԬPMȮV[JXCCCKK oBNq]2z)w*Ϋ ;9q*AS&b4Aju F0*5*,BNhTFjWjFL(P}e `TkT[~Қvl:)`TrrR"tm)S꘹HN\ݍaT`TꌭG>,jTW.:UEi$lzli:)Pp TldN+x:늾Y!:eQY@f)רN)u:k:2ը{ T+P1Rz F0*5E.BW)KU52l3 Tf[j^`GQq*YywsVꔩFk) TV~ F0*5JZNmQNeT7 T}Ze@}u F0*5[յL߿p'}:UYAӿ ^r" ݜU TQQfT[ٹ[َ3g]VNL_~nZbcyxDF-(Peص@TL3K[czg':;q~m,_55KnMJZR$qc35aa&%}㟇.-]>mh!+l6*(P}fwQqwvk*gzИ15gšϳgյݽoӡC-_iք nF`yyO)ը,W Vo%WP}2٨=ΨERO̞y2q(OA.ܸ~3f0i~}in9O=5$8cڍ};7;xn?)بPQɍ:wvqne)os/\vAF5x=l;1AϜKך>~sn6[zeQ!*P]N\Qq*rhuBܩ!nu>K_[uQzČjϟk̀w-l:b3 TLZQq*zɥDF5C\?pO<ҜڨӨۈd|=x=v1*I|B T0*Q9QYzIdTwx(o|#6*X48a^^uWcT T'(P1T/)רM3z )2;,a5*}qcS[ZG~*6grjb T0*Q9QEYdNi?/-m@XԨ*S(PF$Fi"JS:WQK_+*αtT U: `TkTG,]Ln?d`Tr\"t]3}R T0*Q9Q"t603 T@U,N YlC`*+ÿQɨ*@ ʂFfE7a㴑tΜqύm{J\2@ T0*Q9Q4sƍݱBCzL;~P ]PAިK_֒%SQo I#N}EҋзM#4.eAgTV+Pu `TkT,BooS^S/KUN٦@u F0*6*fz=Tfڴ/qp[6U7|:NTƍ<)O6{2;ѿKvm>lQIFUlj1wh%^+4*רe>U'a)f0n$13Rmmk%^jTG)`Trz S٪);5! )4W(P5@%)}S0*Q9QJNx\]*,S9.cTv/P͂QQYըv)#:1ը ztʵ T0*Q9Qm1F%iTA/P-WTQqO3.Q٠@<)ڨUnN\Z@ FF%@f_K`TQn-P}p*`T0*}F *`TyFնV;z F0*JQG3G)`T0*}FØ3 T_;z F0*J¨6r F0*JQm7@KL+P-p\Q FgTl[Q9QY@C `T0*H>}7,Pm蔬յE>=|ΧO'+~6S]QQY着ZOYF2ިZ@Q'm{mW;/N)+mĄN|7 2|ԅ ߆*ԩZ*!!!==NQ["v!_?%#qo_09JKşODN#'QM;8̱ïRIM!/(3ɘ7`T`T1bm}5jʕ+㓒233KJJeUVPK4ϙ˝Yc#~3~1ϴǮQusӡ^|YҬ ?փ|أ_X)kcn^h7^ENPVVJpc@:?t///ojj6lT)Pu7/bF59>#'ʦOI̱XI{;Fۇ_~jԠi7B7>Lh?t9Akk˗IT*Lҩ+W\vMQm1@1q}a-Q|8eR^oJ89%_>k܎~M͖^Fx \~d:-t~,N蠳gRf Tl@~uI>xw?zE-6*^Q͝>;32 U]]]$Uuuut3S__Og7RXMhp}nOJsZN?c-vk׮%Z= :KtԩFEdbw.gTi?~n]w䧾+6*X48aC^غ+)^uCuv且L/PPvBF}#F/?R9S(`)$j T_u7~K{uF`oSh,CuuٳRdȐ!klld+Zij$ T?g US\?0*KԨQy??ƍ~Gή.-1*kW“¬YH@V_7o^ss3]&4Qm0@ *U:tJڵ˫^ƨ֣@Sv&:Gg $|2@DT;O}ϱ aOt:Fɍ cU gddTUUUe*QnaF T_ȴI=D8g|wlaT`T,JNNɹxbcccGGGob:ebzc¦vJG՜2.ɞz`K2FE_J8:o-'^}ķ=i$%M^D] D~;ӗ2ީ-v0p" Rs @Q%$$dddյ3ڞ>S TW/ԨS8r==bK>Ti{njwKF~߆5:y 2%c=z5h;=tԒ/q}=vևHjECG>{ X{͑ ~n09¨QVJw6a8AK =@cT?RH}wC͸-FsߗՙeT_~칌ķy:z2")TMV߾G˒QNm^ۑvteMoJJM.]kR֦ŞjRrf5)Ia:zr^s-7sVMo&'d7q\ZRwa޴O O^eTb1欠J`sSP{yCb,$O(ܼvQ-ג75l=l-C@~;}i `TgTNtqm׹+]jҢI5uq4tSI&OxZP˖PĎJeS ?ryI_2*&S TSGOU_?0pȫz0;WuǥeT.~gcl+32>{pj GF;[90*Q9QNҧSZ.%SuJKuJR|FARŗ$Ǟ sFO{ ͂y _,fΧOaSuÇݫsP*%MgSuxOҴ)2_mLev`?~;i61*R"9:eNiJSKSdȥrԱQm۶-((*222..$ T͋Φ2ޘ4|?z; '2ah \N|ᑏ ,3!L əX>Uou`iJS攦:%Qꔰ4=jӦM$U!!!gT@%)zP\M#l4 Ս$u^J "|} QI~T`T=FEVdZiJS)3}KSZ:euTk׮ݲeKPPСC?IZcrJ3gtք,X(ShL2*-R"t-dZQw gTfϜ:raT`T61*#XSYM)ި1Ղ+Fp'"èFtFeL[L5E}?QwoTFJ[4t*1~0@7)֨*cNmTD fè`TrR"teyFeBj `TkTF _nF 3TZ1FFlR"t EFU_]P`TrXE, )SjYQʑJ%FU)֨Ȑ]1 ,`T-2@z`Tr|"C"ƔpF¸uLQYJ"V^S0tktJ\:rg6*Y3*qQj.HQʂЙN] ,aT TNFFEdE ^8e V^ `Trp"te/Q(P5~ QaT^dlZf@ aTrp"O"tǙ鳄Q+P aTr"tſϢF_ R0*Q9Q]S6[jtD2èJâ`Tr rEN\qF@>èȖl= F0PF4FeE IQ9?.KeU 5癗&'D6- :Qs.y!)4N]_v=)F(GiTKt>?F,Fuc6Y%#oz5b-a;3~F}l33^{Go;=!/:WߥKm̨S^zkьHQjT՗QʹҋɨtK,2"rݟBhڃx6KHFokb9/ʬ3) rdWl$QF/\Pj^@59QMxnnMy?\Ry5O3] fbTsGtS_~'z~|#}jTMk sQʹҋcB>=w* hc뒵ҙ V&+⑅%b9{N*Ԩ?Q[_B{tӨ, ]Ȧ.D9HgJ{{7- 2hT?;j﬘H 7\J5jjLz oQ9l:"!sz'_33uGo~8oӁQ+x?اGT7BIՇ~ר2u`T0*v4*ҚÇ3fŊ#Fظq# .^xܹlϞ=v9rqf̘1p3gNARC!|mGh7WLhѢ $jzD* .QPQRSYYـ~@|Hi}҈oJJJڶm ?_,B`T0*J3*֐QI=ooI :6x` lQuu?>hРS=#3g*((Q`TʨƎ^YYI.]l2>b*jժU$^⑅ T Fo߾t3툈Ç΁>jSXB<O=رc&L0~ٳg?[n6_DzTvtZi///ojj?$ \ݨ3ϼ+^^^t7DSK,!!99Y2^'zh$<<='N8rHv?ooowwwFǓTEFF>|СCaaa.DOyc mT$/_...&RTg菇Lҩ+W\v F0zĪJn[[ێ1b/I \V&! :Kt:%aTdׯ_oooonn&*//'}tZ Y"3AgFu$L2aF%* KI*R^*ƺ:ڡL}}=%:WStPzUrXXSSS.]JGŨ9Hdk׮%ZWۀ.Dt `TFe_ꆆ@?Fr9ȥȨ=z6VbFegfϞ $h(e=wyUVQ`4aaa䬏>h{{{gggWWz~>`8J*`T0*L4ꦦ&* S*77˭0*-?FWVVRo:_ &J;0~t6N8R۱>`TJ1*ҩ͋wqee\m-WW|WuJ;r钎TTh\;jE)-NIvSTB\#ʹs:rv z%?Wrr @jM6dx*iii=TcTtRTK w:mmrʭ §WzWnVH愑:}2g6iWP4 -2}+WzH#GWT*9]fTjK0EJEIL)%-R2-JZdZEFRFARŗ`TRQr\2m۶TEFFű2 `T7*H/Gii(-rrUYY.eT6m" 9|pll, `T`Td-rDʴrHV)QZ"eZ9JKL+Gi\.3utڵklt!~F0*ÕDʴrHV)QZ"oT?ݻ¢aTr"wA9J (&Rt)QFhFeT9e-L+G\%]Q|9e] F0*G6*2t=2sH >Wj:3X끄HEQPQI<([(lR0*Q9QĠer/R.>\̨\QL R0*Q9Q͠EE ˤ`Tr-BNsQpQ"A9r?G"0*QQBNK[D FF6*2t=0QQ0*t=z[D FFۨoP2(RD FFרPiQ7HA`T`T,]D aT`TYw=H,`T.[qB`T`Tf=p WC`T`T7*2'zԷ۷z| `TbT;NVn0ah `TNdTNSHYT O֬g;ҧ))F(ֱ;aT0*Q9Q82sr5H:t3x3&ZoЇ*m%%Gh1FMyE3fHQ`T0*Q9Q9\4'{oH ضt) ʨ(H}_Fُu= )϶.m31yGwԗ_^#3z?`> `ThT(G2*gTl[j\,#*6g F0*dԩzH({w6~:ԗ_^1gx$y57\0*^xQ) 1g'>4}O#s=1F`TrH^FE&)Ҟ,lsiKFa&eTvl©'X 2dTCv,G)01& FE2d&:+R%3cTDz VFerþ_QQjTF旣0QM"C2a9-"CB9 QQeTIZr2Ψ6FDeQ(G!0*0*# A`T`T0*FFB`TB QQF0*FFB`T`T0*Q F U?whdÆ Qj/ϛ6 7rD!:uY|}}:Uhh!Cn~Æ O֬Ynݺ`2nTt;VxxxHHȶm6mD73__9 ۾}޽{#""bccI`TRQ+222444((j˖-~ ҩFEEFt[+SEDDTݻjݻ9tH!+P)?mTLI"##>|С 됮F)2Y F0*_{Uttt=:I^`TrzJ@R0*Q9Qd82pP/^FZre|||RRRfffIII}}}{{; `T=tjhh(++KMM[¡ 5!!3g痗755uvvvwwwlmTtjmm|rqq1IJ:I:URRRSSsʕk׮Feksf*+… Pݗ;::`T(YEYYRENJJJJHH]Q8q~$w/d%֧;QQ(s$ɠ'5I2I1$dГwo4}濷<w)>DxhvB0h;3>'OHĜ1>/[իWaT|!twrם^Dw2hd#9"oǯʸOxG5Tu~k85̌ quN-޼8#+j:Feajq*Tr\.SUh+ }Z]+J WbX+E\V B\.s;/98gZ) r24.W/#;w*=ҩśWu/S_-u\Kvkt2$L4VRNK:R_!J)"W+N:5”hr(ET S rv.QΊRN24W6mϐ!CbbbbccdШHoM)j:>jFW.N5.N(s*N(wR*J wE+ŢRȵj傮s\8gE)|Y)kL\!qT5 eTP4N:µ\Rڸ6apWr!|fa&aFa>\=9aN%pM$MBˤLʕΥTQ?TWAi- ҦN+;-\eiiT [iN>B9K:M-M'R1(WEH#G=nH+Ukr uNi+\i&LS4Nɧ^ޑ:}2g6iWP4 -2}+WkkFeL(iiQ"%ӢEJEIL)%cQes.eTIIIOJ5\Ǣ:tY,oQWeȢĕ0}e0җHiպ$J\Z-H,JO5K\Ī.QFDD `Js `Q"%ӢEJEIL)%-R2-Ꜯ"V6RFQ4K.ߣG$+(G)o*0u)]gϲ?}\ͦZbrTEDʂ(}S\Kնmۂ']\(G)o*0kv)y èn{̨"%%RDʕQ:TeqY.eT :qDFFF~~ШcF%-G:@96( R2M6 ?|pllT"eZ9(H WSeXJ5 2`Td-rDʴrHV)QZ"eZ9JKL+Gi\.7tbFN?iiiyyyl>4prT%Q(GU[UnrT%QL/re\3knٲ?t?5!G,1gZ9(-h 2uΘ\sˬLJ5 6*a(fTW)QZ"eZ9JKL+GiTGqAjݡqqq999b\MR+R.ZUhz9JHiqRr)ި~G¢ÕDʴrEK -Qj:eY"A[.3KwmQYJ]$D,b-p*2),eTz`" R0*\buO([(lRFSNeee3*Xb5M8kVRTw1+AQRFU1*lVY)Dj~ w (xrYèΊ\ hirFԳ{)KI Q6(G"JF]lVb"eAL6aFe&z`r,جD*˕x Q1^_UEE ˤL6}QiYT5\&4/3E ˤlcTd3z`b2)U^oThiNޢ R1;v@Q M8( M8z QݨiP2OHa*99YRGs ס M8Q=S{jzs9 Qf=֛èlfTuQBNK[DF}{ϰ>pPNrDJ!F M8M^f.mQ)ŋu"Ԧŕ끱(*_kM8-GAeTd6z`l9 "RQj ]$QR$R0% QEHoT (e>R)UNNN~Q N.fAĨPiQ=} R0*U8WZ"J0ȤtFE&(G_V?"F-RG1*t=)t6Q)ڨjD8Yw=H9Q \5.ۄSHaYw=HĨ\wH)ߨrʆ i))Wrtrrߍ"r*))e 4Nj(eQĉdTٹ\×T}>pF"qr޲rl,74H)ߨ~={9rDQ`FM8+!RNiTN߄S"rN"qz޾}WgL3'}:F5\M8ྂǩgO-d*v=0-{QZ# N_43`i >'eThtɺXc 'fx<`pϐw+gD¨ҲtT5\dM85hՕn1z٧xd"q&4\F6Ćifx ',fT"Q9M9_ e?EeTQQ߇~`>:ptsf9#bںukpppDDبE0r&LؼE* )MG*i;PN獄)Qm؜UTe  dUrr@ʄ恪͕z>)roQ֜_S:34KRcQEUn N w H#y(YFUUUE$=\eRN3jkc_CCx<<-iTA1hT\U@EY}?;g X %tytyQ dsO4}[Q82sr 0rԘc_{zh3*!~W?x6.xї?3|cֵcɨ4WL/E~?{Kt{>.z?D FeQU `ώmKިt3.Ê?FQI4ÇQяب5zDʊ774wW쮯oOhT~ŠwVM Q9\k4\ +uȗk͌j6}Ψڍ䉍eF:%>_Wv=b?csW-HUQQQ'qdQ6k̷Z(JPVoTO|ǽ=M6Bbm6QeAs W]MNVq+Ώ|)>Xݤi)!6'.ړ aQ8P׃lkdF̏ޝFN0>7JJK䏽g}ǒ0=?qp֞t yQYƨ*++Q3SO%b;RwF(nnQQIv,ІبCdUBBBjjJ3*7]uOMvR[n*\hTI!v|O Q=;yuA"F( :6;;R+U sW-n~,ړL;*>ZQ4Wrχl$,D FeQUɁ^W͉=׉=c ި{Gx)ݓ|#|={\G%?ӨU5\&|9ʾ9+5'Zti^(-Yɨȁ^Y)͕Y91#~ZN#39)YMGh4+FX2;eK}#xFN덫DFeب +EF&63Yfyxy̓l>uQ/59ֱdTt e+h/G}2Ѩ.@dBJz(z_Jgx<-ɨ1v>}:##CK4hKvo[<ƅ\ሸF/Z"7*2!%t=VƳOw. ,UZ7._D|x7"0*FuI3*;6ēaQmٲEQD0cN)t:uJQe+фS2sHAɐ5p\fB`T0 ̨zQFyf}F!Upڱ:FeeC`TV4r'%F6>@ɨ2@˭фS 1*R"kt=qFZFUfT-G)zծ]ǘ~SRR*7̨, KCب,[3E8(!4*QAs لz}*WcTf0`Te=02#6dէS$[̨LzH!v7*Ӻ`9Qq2$c^Qܹ:*7k̨&y=D9FEd2s!0*չstQ*M?kԘrn⌊ (FFu'OҲ(U\\ɓ'SSS"u5\rFEeQ(G!0*EE4N0r&'-B9 Qя;BCC.4p9ɓE8Q=)KdQ(G!0*0RfTXJJJ:}0BG7*Q9Q={YX:p@ll>JFFek*BhT6miTQ!0*Q٨JJJ`TĉNJ Q!FU@ڸq#6ӽQ!0*QԨ JD0Q!0*Qͨ{Q!J6*1NLLLIIԩ`T A`T6b0*DFm6QD_Q!0*QԨaTUBB(R'OEF#0*QèzQ! 4 6틊'NhY Q!ʞFU$F(Ш֯_|!yw#gT0*Diۍ.((&]ø# kFuӨE UѸ# fȽ]çpG :8,ްZ5 eT"&3ϟ7rD!:~Y|}}=<<?~]ԩSK,-JZĵ+6`7"쁯Q9mϟ?sr})++iTtsss~D9Q),fL;Y &EM6N… F$mTǏg+,X G'N~!+[H0*uyzz6446>Lߓֈ>o=22V EA_:wAE콊v\Ӣ4 rJҦ^{#<<\爄Q1SO PСC7p@z;vIA`QfQ)))(cT2Mz4Ⱥ_-JZjQN+oZFeߧFE) Z{, FeТ̜3JO0Im9YMfQ2¨Bz׷{]/pmaT7*33St䌡jBӲsJ+归b(|t䤼$ IԙRudVJnS|+23EN KDH˅^#:s^9rNGeVΪ&;RNhmNٲ:vҴyC a'Q2*'lMVԩ՟\QdYZ{J*YZzTiV\FXԹ?ZiTD24L9ϧNs2"dYB[PaCSye9nխ\FKd-: UVR*DEӲN)G>Q)vDRHj/!7?ryI_Q0*U+ߖjuR5lU-]-]eKmQ-5ȵ YTk5[lDiPlKm)C-UZޖN_3ǖYږ鲥ְ"KkFdLz5Ј*L6*Js[j]TeR\a*ZyX[2T'W./kKyOX^2֖,oKgtR5lH[*-%Ĵ-˵Xl)H[:b$)lz5m۶TEFFű2QT*{Qa2qdsLF,vR z5M6T>|866F%˨膪ɸ2La2Ρ'0g8 $lIgs!yԶLn D qZv-[:OJ;鞊8La2N{1gɸ Vsl4oۛ.Aⶩx[F?޽;,,,::F˨2228dELٵ8db& e>'W#Oe'JQ&Ο՘Sd\&08tVۖB{+;vfq?3wq"d?F*La2qSd>[ Ysvd) 0*}-/a2qsɸMˁͻiN;ԳufFe3JOO?X=^8s'18L?[at!y\pT[D愚oTY"Qa2q;8d-1aߣML;2o FFEwbLa2ΡɸʘӲ%ޙ;9vd9\y:Q9Q1@t{d&08qcs=ڴ]Sj @'Q9Qeq=;d\5&L9d3i:v֗t&QQ48ݼk2. qdٓqZ´gzNgFiT*4NlLa2q7%LzJM7WjڊUM2*::nᘌd&ld8-[/iyJMnThn֛d&0gdЖ(|sٽLp&A`T3 4NpL)v2.qv} &-1a <.0[ȄnTk80g'-șAUjj*t4NuLa2qOL|C AU=&L)f2NKL|f&:FFFE7{La2q:L5,YputL!ry:}444N*8Lƹd-Lp&AaJAddbd¤~̜a% ʲFuF`2q2W߼0mY" +lci Ļ'R߁`TFuF8d1L)(V SpNLh1`tm}|X79}xwss@A`T0^Fu)FE8Lf2R$&'L06|9*g;@KRoЧ)3Q}[0\҉S 0*U/J@ +d]z[g2N,Ll%%.FED}߿^,//*~g}rQcF}{}ƠIhϫڤ6UQQqkOdL =wwJ&WQ;waEeu)U@?^سفS\tSg*^q  F6*RLa2NhK!0-z19Q|~p?FoeWT/O.A`TnT)))̢N qRLƹd[SrnLaBg7* EjN̨&LORžTŁ箺>"Ս'd'Bm5G,XƤ,\ܨwEx)|p]_lNGJ#X|/?6oY 0*6'O ]'d}/R^b&xYs,cB կ18Pv͓}ܿWe4Nz,;pM|F#[ u*!!'d]zKu: l{KD quh 0*й JJJJNN>y$m$&&8I&l6wI0abAQ)ܨ8&,8't^nLqEA`TgTd3֖7\9@" 0*W3*ǝo8~ F=aVAQĥ&Ƅ 2ѨHDi2NkJND9L! ZFEvHS}Ȅ  0*9ퟂ"Q^O }CAFD ε-I! A"q7OvRAFFEfkh1 \B 1\x#ĐP0iBB4rSZL-ur^: bu x{lE ~0cLv)O|b1Ƙrp|`0h2^TTx|bb6vggg"H,SQmmmQTE3IRy;3@(ZZZZ[[K )ʼtRL&(Bp$QNbD"f` * :doc:`FAQ & Troubleshooting ` * `PyPI Project`_ * GitHub_ * Discussion_ * `FreeTDS User Guide`_ Features -------- * Unicode friendly * Python 3 friendly * Works on most popular operating systems * Written in Cython_ for performance * Includes a supported and documented low-level module (``_mssql``) that you can use instead of the DB-API * Supports stored procedures with both return values and output parameters * Supports bulk copy * A comprehensive test suite * Compatible with cooperative multi-tasking systems (gevent, etc.) * Can be used to connect to Azure License ------- pymssql is licensed under the terms of the GNU LGPL license. .. _Project Home: https://github.com/pymssql/pymssql .. _Python: http://www.python.org/ .. _FreeTDS: http://www.freetds.org/ .. _PEP-249: http://www.python.org/dev/peps/pep-0249/ .. _Microsoft SQL Server: http://www.microsoft.com/sqlserver/ .. _Cython: http://cython.org .. _PyPI Project: https://pypi.python.org/pypi/pymssql/ .. _GitHub: https://github.com/pymssql/pymssql .. _FreeTDS User Guide: http://www.freetds.org/userguide/ .. _Discussion: https://groups.google.com/forum/?fromgroups#!forum/pymssql Documentation ------------- .. toctree:: :maxdepth: 2 intro pymssql_examples _mssql_examples release_notes freetds ref/pymssql ref/_mssql migrate_1_x_to_2_x faq building_and_developing freetds_and_dates azure docker changelog todo Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/intro.rst0000644000000000000000000001001200000000000016053 0ustar00rootroot00000000000000============ Introduction ============ Getting started =============== Generally, you will want to install pymssql with: .. code-block:: bash pip install -U pip pip install pymssql Most of the times this should be all what's needed. If you want to build pymssql module against locally installed libraries then install required software (see :doc:`building_and_developing` for more details) and run: .. code-block:: bash pip install --no-binary=pymssql pymssql .. note:: On some Linux distributions `pip` version is too old to support all the flavors of manylinux wheels, so upgrading `pip` is necessary. An example of such distributions would be Ubuntu 18.04 or Python3.6 module in RHEL8 and CentOS8. .. note:: Starting with pymssql version 2.1.3 we provide such wheel packages that bundle a static copy of FreeTDS so no additional dependency download or compilation steps are necessary. .. note:: Starting with pymssql version 2.2.0 official pymssql wheel packages for Linux, Mac OS and Windows have SSL support so they can be used to connect to :doc:`Azure `. * Anaconda / Miniconda A conda install of pymssql will mitigate the need to edit config files outside of the user's home directory on some unix-like systems. This is especially useful when root access is restricted and/or Homebrew can't be installed. This method requires no additional compilation or configuration. .. code-block:: bash conda install pymssql See Installation and :doc:`building_and_developing` for more advanced scenarios. **Docker** (Experimental) Another possible way to get started quickly with pymssql is to use a :doc:`docker` image. .. _domain logins: http://www.freetds.org/userguide/domains.htm Architecture ============ .. image:: images/pymssql-stack.png The pymssql package consists of two modules: * :mod:`pymssql` -- use it if you care about DB-API compliance, or if you are accustomed to DB-API syntax, * :mod:`_mssql` -- use it if you care about performance and ease of use (``_mssql`` module is easier to use than ``pymssql``). And, as of version 2.1.x it uses the services of the ``db-lib`` component of FreeTDS. See the `relevant FreeTDS documentation`_ for additional details. .. _relevant FreeTDS documentation: http://www.freetds.org/which_api.html Supported related software ========================== :Python: Python 3.x: 3.6 or newer. :FreeTDS: 1.2.18 or newer. :Cython: 0.29 or newer. :Microsoft SQL Server: 2005 or newer. Project Discussion ================== Discussions and support take place on pymssql mailing list here: http://groups.google.com/group/pymssql, you can participate via web, e-mail or read-only subscribing to the mailing list feeds. This is the best place to get help, please feel free to drop by and ask a question. Project Status ============== **Current release**: 2.x is the branch under current development. It is a complete rewrite using Cython and the latest FreeTDS libraries (which remove many of the limitations of previous versions of FreeTDS). **Legacy release**: 1.0.3 is the legacy version and is no longer under active development. .. note:: This documentation is for pymssql 2.x. The document set you are reading describes exclusively the code base of pymssql 2.x and newer. All description of functionality, workarounds, limitations, dependencies, etc. of older revisions has been removed. If you need help for building/using pymssql 1.x please refer to the old `Google Code documentation Wiki`_. .. _Google Code documentation Wiki: https://code.google.com/p/pymssql/wiki/Documentation Current Development =================== Official development repositories and issue trackers have been moved to GitHub at https://github.com/pymssql/pymssql. We would be happy to have: * A couple more developers * Help from the community with maintenance of this documentation. If interested, please connect with us on the mailing list. .. _pip: https://pip.pypa.io .. _Python Package Index (PyPI): https://pypi.python.org ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/make.bat0000644000000000000000000001505700000000000015611 0ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pymssql.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pymssql.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/migrate_1_x_to_2_x.rst0000644000000000000000000001261100000000000020400 0ustar00rootroot00000000000000========================== Migrating from 1.x to 2.x ========================== Because of the DB-API standard and because effort was made to make the interface of pymssql 2.x similar to that of pymssql 1.x, there are only a few differences and usually upgrading is pretty easy. There are a few differences though... ``str`` vs. ``unicode`` ======================= Note that we are talking about Python 2, because pymssql 1.x doesn't work on Python 3. pymssql 1.x will return ``str`` instances:: >>> pymssql.__version__ '1.0.3' >>> conn.as_dict = True >>> cursor = conn.cursor() >>> cursor.execute("SELECT 'hello' AS str FROM foo") >>> cursor.fetchall() [{0: 'hello', 'str': 'hello'}] whereas pymssql 2.x will return ``unicode`` instances:: >>> pymssql.__version__ u'2.0.1.2' >>> conn.as_dict = True >>> cursor = conn.cursor() >>> cursor.execute("SELECT 'hello' AS str FROM foo") >>> cursor.fetchall() [{u'str': u'hello'}] If your application has code that deals with ``str`` and ``unicode`` differently, then you may run into issues. You can always convert a ``unicode`` to a ``str`` by encoding:: >>> cursor.execute("SELECT 'hello' AS str FROM foo") >>> s = cursor.fetchone()['str'] >>> s u'hello' >>> s.encode('utf-8') 'hello' Handling of ``uniqueidentifier`` columns ======================================== SQL Server has a data type called `uniqueidentifier `_. In pymssql 1.x, ``uniqueidentifier`` columns are returned in results as byte strings with 16 bytes; if you want a :class:`python:uuid.UUID` instance, then you have to construct it yourself from the byte string:: >>> cursor.execute("SELECT * FROM foo") >>> id_value = cursor.fetchone()['uniqueidentifier'] >>> id_value 'j!\xcf\x14D\xce\xe6B\xab\xe0\xd9\xbey\x0cMK' >>> type(id_value) >>> len(id_value) 16 >>> import uuid >>> id_uuid = uuid.UUID(bytes_le=id_value) >>> id_uuid UUID('14cf216a-ce44-42e6-abe0-d9be790c4d4b') In pymssql 2.x, ``uniqueidentifier`` columns are returned in results as instances of :class:`python:uuid.UUID` and if you want the bytes, like in pymssql 1.x, you have to use :attr:`python:uuid.UUID.bytes_le` to get them:: >>> cursor.execute("SELECT * FROM foo") >>> id_value = cursor.fetchone()['uniqueidentifier'] >>> id_value UUID('14cf216a-ce44-42e6-abe0-d9be790c4d4b') >>> type(id_value) >>> id_value.bytes_le 'j!\xcf\x14D\xce\xe6B\xab\xe0\xd9\xbey\x0cMK' Arguments to ``pymssql.connect`` ================================ The arguments are a little bit different. Some notable differences: In pymssql 1.x, the parameter to specify the host is called ``host`` and it can contain a host and port -- e.g.: :: conn = pymssql.connect(host='SQLHOST:1433') # specified TCP port at a host There are some other syntaxes for the ``host`` parameter that allow using a comma instead of a colon to delimit host and port, to specify Windows hosts, to specify a specific SQL Server instance, etc. :: conn = pymssql.connect(host=r'SQLHOST,5000') # specified TCP port at a host conn = pymssql.connect(host=r'(local)\SQLEXPRESS') # named instance on local machine [Win] In pymssql 2.x, the ``host`` parameter is supported (I am unsure if it has all of the functionality of pymssql 1.x). There is also a parameter to specify the host that is called ``server``. There is a separate parameter called ``port``. :: conn = pymssql.connect(server='SQLHOST', port=1500) Parameter substitution ====================== For parameter substitution, pymssql 2.x supports the ``format`` and ``pyformat`` `PEP 249 paramstyles `_. Note that for ``pyformat``, PEP 249 only shows the example of a string substitution -- e.g.:: %(name)s It is not clear from PEP 249 whether other types should be supported, like:: %(name)d %(name)f However, in this `mailing list thread `_, the general consensus is that the string format should be the only one required. Note that pymssql 2.x does not support ``%(name)d``, whereas pymssql 1.x did. So you may have to change code that uses this notation:: >>> pymssql.__version__ u'2.0.1.2' >>> pymssql.paramstyle 'pyformat' >>> cursor.execute("select 'hello' where 1 = %(name)d", dict(name=1)) Traceback (most recent call last): File "", line 1, in File "pymssql.pyx", line 430, in pymssql.Cursor.execute (pymssql.c:5900) if not self._source._conn.nextresult(): pymssql.ProgrammingError: (102, "Incorrect syntax near '('. DB-Lib error message 20018, severity 15:\n General SQL Server error: Check messages from the SQL Server\n") to:: >>> cursor.execute("select 'hello' where '1' = %(name)s", dict(name='1')) >>> cursor.fetchall() [(u'hello',)] or:: >>> cursor.execute("select 'hello' where 1 = %d", 1) >>> cursor.fetchall() [(u'hello',)] Examples of this problem: * `Google Group post: paramstyle changed? `_ * `GitHub issue #155: pymssql 2.x does not support "%(foo)d" parameter substitution style; pymssql 1.x did `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/pymssql_examples.rst0000644000000000000000000002040400000000000020334 0ustar00rootroot00000000000000==================== ``pymssql`` examples ==================== Example scripts using ``pymssql`` module. Basic features (strict DB-API compliance) ========================================= :: from os import getenv import pymssql server = getenv("PYMSSQL_TEST_SERVER") user = getenv("PYMSSQL_TEST_USERNAME") password = getenv("PYMSSQL_TEST_PASSWORD") conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor() cursor.execute(""" IF OBJECT_ID('persons', 'U') IS NOT NULL DROP TABLE persons CREATE TABLE persons ( id INT NOT NULL, name VARCHAR(100), salesrep VARCHAR(100), PRIMARY KEY(id) ) """) cursor.executemany( "INSERT INTO persons VALUES (%d, %s, %s)", [(1, 'John Smith', 'John Doe'), (2, 'Jane Doe', 'Joe Dog'), (3, 'Mike T.', 'Sarah H.')]) # you must call commit() to persist your data if you don't set autocommit to True conn.commit() cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') row = cursor.fetchone() while row: print("ID=%d, Name=%s" % (row[0], row[1])) row = cursor.fetchone() conn.close() Connecting using Windows Authentication ======================================= When connecting using Windows Authentication, this is how to combine the database's hostname and instance name, and the Active Directory/Windows Domain name and the username. This example uses `raw strings `_ (``r'...'``) for the strings that contain a backslash. :: conn = pymssql.connect( host=r'dbhostname\myinstance', user=r'companydomain\username', password=PASSWORD, database='DatabaseOfInterest' ) Iterating through results ========================= You can also use iterators instead of while loop. :: conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor() cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in cursor: print('row = %r' % (row,)) conn.close() .. note:: Iterators are a pymssql extension to the DB-API. Important note about Cursors ============================ A connection can have only one cursor with an active query at any time. If you have used other Python DBAPI databases, this can lead to surprising results:: c1 = conn.cursor() c1.execute('SELECT * FROM persons') c2 = conn.cursor() c2.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') print( "all persons" ) print( c1.fetchall() ) # shows result from c2 query! print( "John Doe" ) print( c2.fetchall() ) # shows no results at all! In this example, the result printed after ``"all persons"`` will be the result of the *second* query (the list where ``salesrep='John Doe'``) and the result printed after "John Doe" will be empty. This happens because the underlying TDS protocol does not have client side cursors. The protocol requires that the client flush the results from the first query before it can begin another query. (Of course, this is a contrived example, intended to demonstrate the failure mode. Actual use cases that follow this pattern are usually much more complicated.) Here are two reasonable workarounds to this: - Create a second connection. Each connection can have a query in progress, so multiple connections can execute multiple conccurent queries. - use the fetchall() method of the cursor to recover all the results before beginning another query:: c1.execute('SELECT ...') c1_list = c1.fetchall() c2.execute('SELECT ...') c2_list = c2.fetchall() # use c1_list and c2_list here instead of fetching individually from # c1 and c2 Rows as dictionaries ==================== Rows can be fetched as dictionaries instead of tuples. This allows for accessing columns by name instead of index. Note the ``as_dict`` argument. :: conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor(as_dict=True) cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in cursor: print("ID=%d, Name=%s" % (row['id'], row['name'])) conn.close() .. note:: The ``as_dict`` parameter to ``cursor()`` is a pymssql extension to the DB-API. In some cases columns in a result set do not have a name. In such a case if you specify ``as_dict=True`` an exception will be raised:: >>> cursor.execute("SELECT MAX(x) FROM (VALUES (1), (2), (3)) AS foo(x)") Traceback (most recent call last): File "", line 1, in File "pymssql.pyx", line 426, in pymssql.Cursor.execute (pymssql.c:5828) raise ColumnsWithoutNamesError(columns_without_names) pymssql.ColumnsWithoutNamesError: Specified as_dict=True and there are columns with no names: [0] To avoid this exception supply a name for all such columns -- e.g.:: >>> cursor.execute("SELECT MAX(x) AS [MAX(x)] FROM (VALUES (1), (2), (3)) AS foo(x)") >>> cursor.fetchall() [{'MAX(x)': 3}] Using the ``with`` statement (context managers) =============================================== You can use Python's ``with`` statement with connections and cursors. This frees you from having to explicitly close cursors and connections. :: with pymssql.connect(server, user, password, "tempdb") as conn: with conn.cursor(as_dict=True) as cursor: cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in cursor: print("ID=%d, Name=%s" % (row['id'], row['name'])) .. note:: The context manager personality of connections and cursor is a pymssql extension to the DB-API. Calling stored procedures ========================= As of pymssql 2.0.0 stored procedures can be called using the rpc interface of db-lib. :: with pymssql.connect(server, user, password, "tempdb") as conn: with conn.cursor(as_dict=True) as cursor: cursor.execute(""" CREATE PROCEDURE FindPerson @name VARCHAR(100) AS BEGIN SELECT * FROM persons WHERE name = @name END """) cursor.callproc('FindPerson', ('Jane Doe',)) for row in cursor: print("ID=%d, Name=%s" % (row['id'], row['name'])) Using pymssql with cooperative multi-tasking systems ==================================================== .. versionadded:: 2.1.0 You can use the :func:`pymssql.set_wait_callback` function to install a callback function you should write yourself. This callback can yield to another greenlet, coroutine, etc. For example, for gevent_, you could use its :func:`gevent:gevent.socket.wait_read` function:: import gevent.socket import pymssql def wait_callback(read_fileno): gevent.socket.wait_read(read_fileno) pymssql.set_wait_callback(wait_callback) The above is useful if you're say, running a Gunicorn_ server with the gevent worker. With this callback in place, when you send a query to SQL server and are waiting for a response, you can yield to other greenlets and process other requests. This is super useful when you have high concurrency and/or slow database queries and lets you use less Gunicorn worker processes and still handle high concurrency. .. note:: set_wait_callback() is a pymssql extension to the DB-API 2.0. .. _gevent: http://gevent.org .. _wait_read: http://gevent.org/gevent.socket.html#gevent.socket.wait_read .. _Gunicorn: http://gunicorn.org Bulk copy ========= .. versionadded:: 2.2.0 The fastest way to insert data to a SQL Server table is often to use the bulk copy functions, for example:: conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor() cursor.execute(""" CREATE TABLE example ( col1 INT NOT NULL, col2 INT NOT NULL ) """) cursor.close() conn.bulk_copy("example", [(1, 2)] * 1000000) .. note:: ``bulk_copy`` does not verify columns data type. For more detail on fast data loading in SQL Server, including on bulk copy, read `The data loading performance guide`_ from Microsoft. .. _The data loading performance guide: https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008/dd425070(v=sql.100)?redirectedfrom=MSDN ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5924108 pymssql-2.2.11/docs/ref/0000755000000000000000000000000000000000000014750 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/ref/_mssql.rst0000644000000000000000000004262200000000000017006 0ustar00rootroot00000000000000=========================== ``_mssql`` module reference =========================== .. module:: _mssql Complete documentation of ``_mssql`` module classes, methods and properties. Module-level symbols ==================== .. data:: __version__ See :data:`pymssql.__version__`. .. data:: VERSION See :data:`pymssql.VERSION`. .. versionadded:: 2.2.0 .. data:: __full_version__ See :data:`pymssql.__full_version__`. Variables whose values you can change to alter behavior on a global basis: .. data:: login_timeout Timeout for connection and login in seconds, default 60. .. data:: min_error_severity Minimum severity of errors at which to begin raising exceptions. The default value of 6 should be appropriate in most cases. Functions ========= .. function:: get_dbversion() Wrapper around DB-Library's ``dbversion()`` function which returns the version of FreeTDS (actually the version of DB-Lib) in string form. E.g. ``"freetds v1.2.5"``. .. versionadded:: 2.2.0 .. function:: set_max_connections(number) Sets maximum number of simultaneous connections allowed to be open at any given time. Default is 25. .. function:: get_max_connections() Gets current maximum number of simultaneous connections allowed to be open at any given time. ``MSSQLConnection`` class ========================= .. class:: MSSQLConnection This class represents an MS SQL database connection. You can make queries and obtain results through a database connection. You can create an instance of this class by calling :func:`_mssql.connect()`. It accepts the following arguments. Note that you can use keyword arguments, instead of positional arguments. :param str server: Database server and instance you want to connect to. Valid examples are: * ``r'.\SQLEXPRESS'`` -- SQLEXPRESS instance on local machine (Windows only) * ``r'(local)\SQLEXPRESS'`` -- Same as above (Windows only) * ``'SQLHOST'`` -- Default instance at default port (Windows only) * ``'SQLHOST'`` -- Specific instance at specific port set up in freetds.conf (Linux/\*nix only) * ``'SQLHOST,1433'`` -- Specified TCP port at specified host * ``'SQLHOST:1433'`` -- The same as above * ``'SQLHOST,5000'`` -- If you have set up an instance to listen on port 5000 * ``'SQLHOST:5000'`` -- The same as above :param str encryption: Specify if encryption is desired. Supported for Microsoft servers. Possible values are: * ``'off'`` -- disables encryption * ``'request'`` -- means use if available * ``'require'`` -- means create and allow encrypted connections only .. versionadded:: 2.2.8 :param str user: Database user to connect as :param str password: User's password :param str charset: Character set name to set for the connection. :param str database: The database you want to initially to connect to; by default, *SQL Server* selects the database which is set as the default for the specific user :param str appname: Set the application name to use for the connection :param str port: the TCP port to use to connect to the server :param str tds_version: TDS protocol version to ask for. Default value: ``None`` :param conn_properties: SQL queries to send to the server upon connection establishment. Can be a string or another kind of iterable of strings. Default value: .. code-block:: sql SET ARITHABORT ON; SET CONCAT_NULL_YIELDS_NULL ON; SET ANSI_NULLS ON; SET ANSI_NULL_DFLT_ON ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET ANSI_NULL_DFLT_ON ON; SET CURSOR_CLOSE_ON_COMMIT ON; SET QUOTED_IDENTIFIER ON; SET TEXTSIZE 2147483647; -- http://msdn.microsoft.com/en-us/library/aa259190%28v=sql.80%29.aspx .. versionadded:: 2.1.1 The *conn_properties* parameter. .. versionchanged:: 2.1.1 Before 2.1.1, the initialization queries now specified by *conn_properties* wasn't customizable and its value was hard-coded to the literal shown above. .. note:: If you need to connect to Azure read the relevant :doc:`topic `. .. versionadded:: 2.1.1 The ability to connect to Azure. .. versionchanged:: 2.2.0 The default value of the *tds_version* parameter was changed to ``None``. Between versions 2.0.0 and 2.1.2 its default value was ``'7.1'``. .. warning:: The *tds_version* parameter has a default value of ``None``. This means two things: #. You can't rely anymore in the old ``'7.1'`` default value and #. Now you'll need to either * Specify its value explicitly by passing a value for this parameter or * Configure it using facilities provided by FreeTDS (see `here `__ and `here `__) This might look cumbersome but at the same time means you can now fully configure the characteristics of a connection to SQL Server when using pymssql/_mssql without using a stanza for the server in the ``freetds.conf`` file or even with no ``freetds.conf`` at all. Starting with pymssql version 2.0.0 and up to version 2.1.2 it was already possible to set the TDS protocol version to ask for when connecting to the server but version 7.1 was used if not specified. .. warning:: FreeTDS added sopport for TDS protocol version 7.3 in version 0.95. You need to be careful of not asking for TDS 7.3 if you know the undelying FreeTDS used by pymssql is version 0.91 as it won't raise any error nor keep you from passing such an invalid value. .. warning:: FreeTDS added support for TDS protocol version 7.3 in version 0.95. You need to be careful of not asking for TDS 7.3 if you know the undelying FreeTDS used by pymssql is older as it won't raise any error nor keep you from passing such an invalid value. ``MSSQLConnection`` object properties ------------------------------------- .. attribute:: MSSQLConnection.connected ``True`` if the connection object has an open connection to a database, ``False`` otherwise. .. attribute:: MSSQLConnection.charset Character set name that was passed to _mssql.connect(). .. attribute:: MSSQLConnection.identity Returns identity value of last inserted row. If previous operation did not involve inserting a row into a table with identity column, None is returned. Example usage -- assume that persons table contains an identity column in addition to name column:: conn.execute_non_query("INSERT INTO persons (name) VALUES('John Doe')") print "Last inserted row has id = " + conn.identity .. attribute:: MSSQLConnection.query_timeout Query timeout in seconds, default is 0, which means to wait indefinitely for results. Due to the way DB-Library for C works, setting this property affects all connections opened from the current Python script (or, very technically, all connections made from this instance of dbinit()). .. attribute:: MSSQLConnection.rows_affected Number of rows affected by last query. For SELECT statements this value is only meaningful after reading all rows. .. attribute:: MSSQLConnection.debug_queries If set to true, all queries are printed to stderr after formatting and quoting, just before being sent to *SQL Server*. It may be helpful if you suspect problems with formatting or quoting. .. attribute:: MSSQLConnection.tds_version The TDS version used by this connection. Can be one of ``4.2``, ``5.0`` ``7.0``, ``7.1``, ``7.2``, ``7.3`` or ``None`` if no TDS version could be detected. .. versionchanged:: 2.1.4 For correctness and consistency the value used to indicate TDS 7.1 changed from ``8.0`` to ``7.1`` on pymssql 2.1.4. .. versionchanged:: 2.1.3 ``7.3`` was added as a possible value. .. attribute:: MSSQLConnection.tds_version_tuple .. versionadded:: 2.2.0 The TDS version used by this connection in tuple form which is more easily handled (parse, compare) programmatically. Can be one of ``(4, 2)``, ``(5, 0)``, ``(7, 0)``, ``(7, 1)``, ``(7, 2)``, ``(7, 3)`` or ``None`` if no TDS version could be detected. ``MSSQLConnection`` object methods ---------------------------------- .. method:: MSSQLConnection.cancel() Cancel all pending results from the last SQL operation. It can be called more than one time in a row. No exception is raised in this case. .. method:: MSSQLConnection.close() Close the connection and free all memory used. It can be called more than one time in a row. No exception is raised in this case. .. method:: MSSQLConnection.execute_query(query_string) MSSQLConnection.execute_query(query_string, params) This method sends a query to the *MS SQL Server* to which this object instance is connected. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. After calling this method you may iterate over the connection object to get rows returned by the query. You can use Python formatting and all values get properly quoted. Please see examples for details. This method is intented to be used on queries that return results, i.e. ``SELECT.`` .. method:: MSSQLConnection.execute_non_query(query_string) MSSQLConnection.execute_non_query(query_string, params) This method sends a query to the *MS SQL Server* to which this object instance is connected. After completion, its results (if any) are discarded. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. You can use Python formatting and all values get properly quoted. Please see examples for details. This method is useful for ``INSERT``, ``UPDATE``, ``DELETE``, and for Data Definition Language commands, i.e. when you need to alter your database schema. .. method:: MSSQLConnection.execute_scalar(query_string) MSSQLConnection.execute_scalar(query_string, params) This method sends a query to the *MS SQL Server* to which this object instance is connected, then returns first column of first row from result. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. You can use Python formatting and all values get properly quoted. Please see examples for details. This method is useful if you want just a single value from a query, as in the example below. This method works in the same way as ``iter(conn).next()[0]``. Remaining rows, if any, can still be iterated after calling this method. Example usage:: count = conn.execute_scalar("SELECT COUNT(*) FROM employees") .. method:: MSSQLConnection.execute_row(query_string) MSSQLConnection.execute_row(query_string, params) This method sends a query to the *MS SQL Server* to which this object instance is connected, then returns first row of data from result. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. You can use Python formatting and all values get properly quoted. Please see examples for details. This method is useful if you want just a single row and don't want or don't need to iterate over the connection object. This method works in the same way as ``iter(conn).next()`` to obtain single row. Remaining rows, if any, can still be iterated after calling this method. Example usage:: empinfo = conn.execute_row("SELECT * FROM employees WHERE empid=10") .. method:: MSSQLConnection.get_header() This method is infrastructure and doesn't need to be called by your code. It gets the Python DB-API compliant header information. Returns a list of 7-element tuples describing current result header. Only name and DB-API compliant type is filled, rest of the data is ``None``, as permitted by the specs. .. method:: MSSQLConnection.init_procedure(name) Create an MSSQLStoredProcedure object that will be used to invoke thestored procedure with the given name. .. method:: MSSQLConnection.nextresult() Move to the next result, skipping all pending rows. This method fetches and discards any rows remaining from current operation, then it advances to next result (if any). Returns ``True`` value if next set is available, ``None`` otherwise. An exception is raised on failure. .. method:: MSSQLConnection.select_db(dbname) This function makes the given database the current one. An exception is raised on failure. .. method:: MSSQLConnection.__iter__() MSSQLConnection.next() .. versionadded:: 2.1.0 These methods implement the Python iterator protocol. You most likely will not call them directly, but indirectly by using iterators. .. method:: MSSQLConnection.set_msghandler(handler) .. versionadded:: 2.1.1 This method allows setting a message handler function for the connection to allow a client to gain access to the messages returned from the server. The signature of the message handler function *handler* passed to this method must be:: def my_msg_handler(msgstate, severity, srvname, procname, line, msgtext): # The body of the message handler. *msgstate*, *severity* and *line* will be integers, *srvname*, *procname* and *msgtext* will be strings. ``MSSQLStoredProcedure`` class ============================== .. class:: MSSQLStoredProcedure This class represents a stored procedure. You create an object of this class by calling the :meth:`~MSSQLConnection.init_procedure()` method on :class:`MSSQLConnection` object. ``MSSQLStoredProcedure`` object properties ------------------------------------------ .. attribute:: MSSQLStoredProcedure.connection An underlying MSSQLConnection object. .. attribute:: MSSQLStoredProcedure.name The name of the procedure that this object represents. .. attribute:: MSSQLStoredProcedure.parameters The parameters that have been bound to this procedure. ``MSSQLStoredProcedure`` object methods --------------------------------------- .. method:: MSSQLStoredProcedure.bind(value, dbtype, name=None, \ output=False, null=False, max_length=-1) This method binds a parameter to the stored procedure. *value* and *dbtype* are mandatory arguments, the rest is optional. :param value: Is the value to store in the parameter. :param dbtype: Is one of: ``SQLBINARY``, ``SQLBIT``, ``SQLBITN``, ``SQLCHAR``, ``SQLDATETIME``, ``SQLDATETIM4``, ``SQLDATETIMN``, ``SQLDECIMAL``, ``SQLFLT4``, ``SQLFLT8``, ``SQLFLTN``, ``SQLIMAGE``, ``SQLINT1``, ``SQLINT2``, ``SQLINT4``, ``SQLINT8``, ``SQLINTN``, ``SQLMONEY``, ``SQLMONEY4``, ``SQLMONEYN``, ``SQLNUMERIC``, ``SQLREAL``, ``SQLTEXT``, ``SQLVARBINARY``, ``SQLVARCHAR``, ``SQLUUID``. :param name: Is the name of the parameter. Needs to be in ``"@name"`` form. :param output: Is the direction of the parameter. ``True`` indicates that it is an output parameter i.e. it returns a value after procedure execution (in SQL DDL they are declared by using the ``"output"`` suffix, e.g. ``"@aname varchar(10) output"``). :param null: Boolean. Signals than NULL must be the value to be bound to the argument of this input parameter. :param max_length: Is the maximum data length for this parameter to be returned from the stored procedure. .. method:: MSSQLStoredProcedure.execute() Execute the stored procedure. Module-level exceptions ======================= Exception hierarchy:: MSSQLException | +-- MSSQLDriverException | +-- MSSQLDatabaseException .. exception:: MSSQLDriverException ``MSSQLDriverException`` is raised whenever there is a problem within ``_mssql`` -- e.g. insufficient memory for data structures, and so on. .. exception:: MSSQLDatabaseException ``MSSQLDatabaseException`` is raised whenever there is a problem with the database -- e.g. query syntax error, invalid object name and so on. In this case you can use the following properties to access details of the error: .. attribute:: MSSQLDatabaseException.number The error code, as returned by *SQL Server*. .. attribute:: MSSQLDatabaseException.severity The so-called severity level, as returned by *SQL Server*. If value of this property is less than the value of :data:`_mssql.min_error_severity`, such errors are ignored and exceptions are not raised. .. attribute:: MSSQLDatabaseException.state The third error code, as returned by *SQL Server*. .. attribute:: MSSQLDatabaseException.message The error message, as returned by *SQL Server*. You can find an example of how to use this data at the bottom of :doc:`_mssql examples page `. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/ref/pymssql.rst0000644000000000000000000004252400000000000017221 0ustar00rootroot00000000000000============================ ``pymssql`` module reference ============================ .. module:: pymssql Complete documentation of ``pymssql`` module classes, methods and properties. Module-level symbols ==================== .. data:: __version__ pymssql version as an Unicode constant. E.g. ``u"2.1.1"``, ``u"2.2.0"`` .. data:: VERSION pymssql version in tuple form which is more easily handled (parse, compare) programmatically. E.g. ``(2, 1, 1)``, ``(2, 2, 0)`` .. versionadded:: 2.2.0 .. data:: __full_version__ pymssql version as an Unicode constant but including any (:pep:`440`) suffixes. E.g. ``u"2.1.0.dev2"``, ``u"2.2.0.dev"`` Constants, required by the DB-API 2.0 specification: .. data:: apilevel ``'2.0'`` -- ``pymssql`` strives for compliance with DB-API 2.0. .. data:: paramstyle ``'pyformat'`` -- ``pymssql`` uses extended python format codes. .. data:: threadsafety ``1`` -- Module may be shared, but not connections. Functions ========= .. function:: connect(server='.', user=None, password=None, database='', \ timeout=0, login_timeout=60, charset='UTF-8', \ as_dict=False, host='', appname=None, port='1433',\ conn_properties=None, autocommit=False, tds_version=None) Constructor for creating a connection to the database. Returns a :class:`Connection` object. Note that in most cases you will want to use keyword arguments, instead of positional arguments. :param str server: database host :param str user: database user to connect as :param str password: user's password :param str database: The database to initialize the connection with. By default *SQL Server* selects the database which is set as default for specific user :param int timeout: query timeout in seconds, default ``0`` (no timeout) :param int login_timeout: timeout for connection and login in seconds, default ``60`` :param str charset: character set with which to connect to the database :param bool as_dict: Whether rows should be returned as dictionaries instead of tuples. You can access columns by 0-based index or by name. Please see :doc:`examples ` :param str host: Database host and instance you want to connect to. Valid examples are: * ``r'.\SQLEXPRESS'`` -- SQLEXPRESS instance on local machine (Windows only) * ``r'(local)\SQLEXPRESS'`` -- same as above (Windows only) * ``'SQLHOST'`` -- default instance at default port (Windows only) * ``'SQLHOST'`` -- specific instance at specific port set up in freetds.conf (Linux/\*nix only) * ``'SQLHOST,1433'`` -- specified TCP port at specified host * ``'SQLHOST:1433'`` -- the same as above * ``'SQLHOST,5000'`` -- if you have set up an instance to listen on port 5000 * ``'SQLHOST:5000'`` -- the same as above ``'.'`` (the local host) is assumed if host is not provided. :keyword str appname: Set the application name to use for the connection :keyword str port: the TCP port to use to connect to the server :keyword conn_properties: SQL queries to send to the server upon connection establishment. Can be a string or another kind of iterable of strings. Default value: See :class:`_mssql.connect() <_mssql.MSSQLConnection>` :keyword bool autocommit: Whether to use default autocommiting mode or not :keyword str tds_version: TDS protocol version to use .. warning:: Currently, setting *timeout* or *login_timeout* has a process-wide effect because the FreeTDS db-lib API functions used to implement such timeouts have a global effect. .. note:: If you need to connect to Azure read the relevant :doc:`topic `. .. versionadded:: 2.1.1 The ability to connect to Azure. .. versionadded:: 2.1.1 The *conn_properties* parameter. .. versionadded:: 2.1.1 The *autocommit* parameter. .. versionadded:: 2.1.2 The *tds_version* parameter. .. versionchanged:: 2.2.0 The default value of the *tds_version* parameter was changed to ``None``. In version 2.1.2 its default value was ``'7.1'``. .. warning:: The *tds_version* parameter has a default value of ``None``. This means two things: #. You can't rely anymore in the old ``'7.1'`` default value and #. Now you'll need to either * Specify its value explicitly by passing a value for this parameter or * Configure it using facilities provided by FreeTDS (see `here `__ and `here `__) This might look cumbersome but at the same time means you can now fully configure the characteristics of a connection to SQL Server from Python code when using pymssql without using a stanza for the server in the ``freetds.conf`` file or even with no ``freetds.conf`` at all. Up to version 2.1.1 it simply wasn't possible to control the TDS protocol version, and in version 2.1.2 it was possible to set it but version 7.1 was used if not specified. .. warning:: FreeTDS added sopport for TDS protocol version 7.3 in version 0.95. You need to be careful of not asking for TDS 7.3 if you know the undelying FreeTDS used by pymssql is version 0.91 as it won't raise any error nor keep you from passing such an invalid value. .. warning:: FreeTDS added support for TDS protocol version 7.3 in version 0.95. You need to be careful of not asking for TDS 7.3 if you know the undelying FreeTDS used by pymssql is older as it won't raise any error nor keep you from passing such an invalid value. .. function:: get_dbversion() Wrapper around DB-Library's ``dbversion()`` function which returns the version of FreeTDS (actually the version of DB-Lib) in string form. E.g. ``"freetds v1.2.5"``. A pymssql extension to the DB-API 2.0. .. function:: set_max_connections(number) Sets maximum number of simultaneous database connections allowed to be open at any given time. Default is 25. A pymssql extension to the DB-API 2.0. .. function:: get_max_connections() Gets current maximum number of simultaneous database connections allowed to be open at any given time. A pymssql extension to the DB-API 2.0. .. function:: set_wait_callback(wait_callback_callable) .. versionadded:: 2.1.0 Allows pymssql to be used along cooperative multi-tasking systems and have it call a callback when it's waiting for a response from the server. The passed callback callable should receive one argument: The file descriptor/handle of the network socket connected to the server, so its signature must be:: def wait_callback_callable(read_fileno): #... pass Its body should invoke the appropiate API of the multi-tasking framework you are using use that results in the current greenlet yielding the CPU to its siblings whilst there isn't incoming data in the socket. See the :doc:`pymssql examples document ` for a more concrete example. A pymssql extension to the DB-API 2.0. .. function:: version_info() .. versionadded:: 2.2.0 Returns string with version information about pymssql, FreeTDS, Python and OS. Please include the output of this function when reporting bus etc.:: //python -c "import pymssql; print(pymssql.version_info())" A pymssql extension to the DB-API 2.0. ``Connection`` class ==================== .. class:: Connection(user, password, host, database, timeout, \ login_timeout, charset, as_dict) This class represents an MS SQL database connection. You can create an instance of this class by calling constructor :func:`pymssql.connect()`. Connection object properties ---------------------------- This class has no useful properties and data members. Connection object methods ------------------------- .. method:: Connection.autocommit(status) Where *status* is a boolean value. This method turns autocommit mode on or off. By default, autocommit mode is off, what means every transaction must be explicitly committed if changed data is to be persisted in the database. You can turn autocommit mode on, what means every single operation commits itself as soon as it succeeds. A pymssql extension to the DB-API 2.0. .. method:: Connection.close() Close the connection. .. method:: Connection.cursor() Return a cursor object, that can be used to make queries and fetch results from the database. .. method:: Connection.commit() Commit current transaction. You must call this method to persist your data if you leave autocommit at its default value, which is ``False``. See also :doc:`pymssql examples `. .. method:: Connection.rollback() Roll back current transaction. .. method:: Connection.bulk_copy(self, table_name, elements, column_ids=None, batch_size=1000, tablock=False, check_constraints=False, fire_triggers=False) .. versionadded:: 2.2.0 Insert data into the target table using the Bulk Copy protocol. :param str table_name: The name of the target table. :param List[Tuple] elements: The data to insert. :param List[int] column_ids: The IDs of the target columns. The first column in a table is index 1. If unset will default to n, where n is the number of elements in each tuple passed as data. :param int batch_size: Commit rows to the target table for every batch_size rows, defaults to 1_000. :param bool tablock: Set TABLOCK hint. :param bool check_constraints: Set CHECK_CONSTRAINTS hint. :param bool fire_triggers: Set FIRE_TRIGGERS hint. ``Cursor`` class ================ .. class:: Cursor This class represents a Cursor (in terms of Python DB-API specs) that is used to make queries against the database and obtaining results. You create ``Cursor`` instances by calling :py:meth:`~Connection.cursor()` method on an open :py:class:`Connection` connection object. Cursor object properties ------------------------ .. attribute:: Cursor.rowcount Returns number of rows affected by last operation. In case of ``SELECT`` statements it returns meaningful information only after all rows have been fetched. .. attribute:: Cursor.connection This is the extension of the DB-API specification. Returns a reference to the connection object on which the cursor was created. .. attribute:: Cursor.lastrowid This is the extension of the DB-API specification. Returns identity value of last inserted row. If previous operation did not involve inserting a row into a table with identity column, ``None`` is returned. .. attribute:: Cursor.rownumber This is the extension of the DB-API specification. Returns current 0-based index of the cursor in the result set. Cursor object methods --------------------- .. method:: Cursor.close() Close the cursor. The cursor is unusable from this point. .. method:: Cursor.execute(operation) Cursor.execute(operation, params) *operation* is a string and *params*, if specified, is a simple value, a tuple, a dict, or ``None``. Performs the operation against the database, possibly replacing parameter placeholders with provided values. This should be preferred method of creating SQL commands, instead of concatenating strings manually, what makes a potential of `SQL Injection attacks`_. This method accepts formatting similar to Python's builtin :ref:`string interpolation operator `. However, since formatting and type conversion is handled internally, only the ``%s`` and ``%d`` placeholders are supported. Both placeholders are functionally equivalent. Keyed placeholders are supported if you provide a dict for *params*. If you call ``execute()`` with one argument, the ``%`` sign loses its special meaning, so you can use it as usual in your query string, for example in ``LIKE`` operator. See the :doc:`examples `. You must call :meth:`Connection.commit()` after ``execute()`` or your data will not be persisted in the database. You can also set ``connection.autocommit`` if you want it to be done automatically. This behaviour is required by DB-API, if you don't like it, just use the :mod:`_mssql` module instead. .. method:: Cursor.executemany(operation, params_seq) *operation* is a string and *params_seq* is a sequence of tuples (e.g. a list). Execute a database operation repeatedly for each element in parameter sequence. .. method:: Cursor.fetchone() Fetch the next row of a query result, returning a tuple, or a dictionary if as_dict was passed to ``pymssql.connect()``, or ``None`` if no more data is available. Raises ``OperationalError`` (:pep:`249#operationalerror`) if previous call to ``execute*()`` did not produce any result set or no call was issued yet. .. method:: Cursor.fetchmany(size=None) Fetch the next batch of rows of a query result, returning a list of tuples, or a list of dictionaries if *as_dict* was passed to :func:`pymssql.connect()`, or an empty list if no more data is available. You can adjust the batch size using the *size* parameter, which is preserved across many calls to this method. Raises ``OperationalError`` (:pep:`249#operationalerror`) if previous call to ``execute*()`` did not produce any result set or no call was issued yet. .. method:: Cursor.fetchall() Fetch all remaining rows of a query result, returning a list of tuples, or a list of dictionaries if as_dict was passed to ``pymssql.connect()``, or an empty list if no more data is available. Raises ``OperationalError`` (:pep:`249#operationalerror`) if previous call to ``execute*()`` did not produce any result set or no call was issued yet. .. method:: Cursor.nextset() This method makes the cursor skip to the next available result set, discarding any remaining rows from the current set. Returns ``True`` value if next result is available, ``None`` if not. .. method:: Cursor.__iter__() Cursor.next() These methods facilitate :ref:`Python iterator protocol `. You most likely will not call them directly, but indirectly by using iterators. A pymssql extension to the DB-API 2.0. .. method:: Cursor.setinputsizes() Cursor.setoutputsize() These methods do nothing, as permitted by DB-API specs. Exceptions ========== .. exception:: StandardError Root of the exception hierarchy. .. exception:: Warning Raised for important warnings like data truncations while inserting, etc. A subclass of :exc:`StandardError`. .. exception:: Error Base class of all other error exceptions. You can use this to catch all errors with one single except statement. A subclass of :exc:`StandardError`. .. exception:: InterfaceError Raised for errors that are related to the database interface rather than the database itself. A subclass of :exc:`Error`. .. exception:: DatabaseError Raised for errors that are related to the database. A subclass of :exc:`Error`. .. exception:: DataError Raised for errors that are due to problems with the processed data like division by zero, numeric value out of range, etc. A subclass of :exc:`DatabaseError`. .. exception:: OperationalError Raised for errors that are related to the database's operation and not necessarily under the control of the programmer, e.g. an unexpected disconnect occurs, the data source name is not found, a transaction could not be processed, a memory allocation error occurred during processing, etc. A subclass of :exc:`DatabaseError`. .. exception:: IntegrityError Raised when the relational integrity of the database is affected, e.g. a foreign key check fails. A subclass of :exc:`DatabaseError`. .. exception:: InternalError Raised when the database encounters an internal error, e.g. the cursor is not valid anymore, the transaction is out of sync, etc. A subclass of :exc:`DatabaseError`. .. exception:: ProgrammingError Raised for programming errors, e.g. table not found or already exists, syntax error in the SQL statement, wrong number of parameters specified, etc. A subclass of :exc:`DatabaseError`. .. exception:: NotSupportedError Raised in case a method or database API was used which is not supported by the database, e.g. requesting a :meth:`~Connection.rollback()` on a connection that does not support transaction or has transactions turned off. A subclass of :exc:`DatabaseError`. .. exception:: ColumnsWithoutNamesError Raised by :meth:`Cursor.execute` when ``as_dict=True`` has been specified to :func:`open ` the :class:`connection ` and the query sent to the server doesn't involve columns names in its results. A subclass of :exc:`InterfaceError`. .. note:: ``ColumnsWithoutNamesError`` isn't a PEP-249-mandated exception but rather a pymssql extension. .. _SQL Injection attacks: http://en.wikipedia.org/wiki/SQL_injection ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/release_notes.rst0000644000000000000000000000754700000000000017573 0ustar00rootroot00000000000000============= Release notes ============= Release notes -- All breaking changes and other noteworthy things. pymssql 2.0.0 ============= This is a new major version of pymssql. It is totally rewritten from scratch in Cython. Our goals for this version were to: * Provide support for Python 3.0 and newer, * Implement support for stored procedures, * Rewrite DB-API compilant pymssql module in C (actually in Cython) for increased performance, * Clean up the module API and the code. That's why we decided to bump major version number. Unfortunately new version introduces incompatible changes in API. Existing scripts may not work with it, and you'll have to audit them. If you care about compatibility, just continue using pymssql 1.0.x and slowly move to 2.0. Project hosting has also changed. Now pymssql is developed on GitHub: http://github.com/pymssql/pymssql. Credits for the release go to: * Marc Abramowitz who joined the project in Jan 2013 and is responsible for the actual release of the 2.0 version by fixing many old tickets, coding the port to Python 3 and driving the migration to Git and GitHub. * Randy Syring who converted the repository to Mercurial, extended tests and ported them to nose, enhanced the code in several fronts like multi-platform (compilers, OSes) compatibility, error handling, support of new data types, SQLAlchemy compatibility and expanded the documentation. * Damien Churchill who set the foundations of the new Cython-based code base, release engineering, new site features like Sphinx, SimpleJSON and others, * Andrzej Kukuła who did all the docs, site migration, and other boring but necessary stuff. * Jooncheol Park who did develop the initial version of pymssql (until 0.5.2). Now just doing boring translation docs for Korean. ``pymssql`` module ------------------ * Rewritten from scratch in C, you should observe even better performance than before * ``dsn`` parameter to :func:`pymssql.connect()` has been removed * ``host`` parameter to :func:`pymssql.connect()` has been renamed to ``server`` to be consistent with ``_mssql`` module * ``max_conn`` parameter to :func:`pymssql.connect()` has been removed ``Connection`` class ~~~~~~~~~~~~~~~~~~~~ * ``autocommit()`` function has been changed to :attr:`pymssql.Connection.autocommit` property that you can set or get its current state. ``Cursor`` class ~~~~~~~~~~~~~~~~ * ``fetchone_asdict()`` method has been removed. Just use :func:`pymssql.connect()` with ``as_dict=True``, then use regular :meth:`~pymssql.Cursor.fetchone()` * ``fetchmany_asdict()`` method has been removed. Just use :func:`pymssql.connect()` with ``as_dict=True``, then use regular :meth:`~pymssql.Cursor.fetchmany()` * ``fetchall_asdict()`` method has been removed. Just use :func:`pymssql.connect()` with ``as_dict=True``, then use regular :meth:`~pymssql.Cursor.fetchall()` ``_mssql`` module ----------------- * Added native support for stored procedures (:class:`~_mssql.MSSQLStoredProcedure` class) * ``maxconn`` parameter to :func:`_mssql.connect()` has been removed * ``timeout`` and ``login_timeout`` parameter to :func:`_mssql.connect()` has been added * :func:`~_mssql.get_max_connections()` and :func:`~_mssql.set_max_connections()` module-level methods have been added * Class names have changed: ====================== ====================== Old Name New name ====================== ====================== MssqlException MSSQLException MssqlDriverException MSSQLDriverException MssqlDatabaseException MSSQLDatabaseException MssqlRowIterator MSSQLRowIterator MssqlConnection MSSQLConnection ====================== ====================== ``MSSQLConnection`` class ~~~~~~~~~~~~~~~~~~~~~~~~~ * Added :attr:`~_mssql.MSSQLConnection.tds_version` property. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/requirements.txt0000644000000000000000000000013500000000000017457 0ustar00rootroot00000000000000setuptools_scm[toml]>=5.0,<7.0 sphinx sphinx_rtd_theme sphinx-copybutton sphinx_toggleprompt ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/docs/todo.rst0000644000000000000000000000007300000000000015673 0ustar00rootroot00000000000000==== TODO ==== Documentation ============= .. todolist:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/pyproject.toml0000644000000000000000000000044400000000000016162 0ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=54.0", "setuptools_scm[toml]>=5.0,<7.0", "wheel>=0.36.2", "Cython>=0.29.32" ] [tool.setuptools_scm] write_to = "src/pymssql/version.h" write_to_template = '#define PYMSSQL_VERSION "{version}"' local_scheme = "no-local-version" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/pytest.ini0000644000000000000000000000067700000000000015307 0ustar00rootroot00000000000000[pytest] norecursedirs = build docs/_build SQLAlchemy* stuff venv *.egg .tox *.venv addopts = # --verbose --tb short # --capture no # show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed. -rfEsxX # --junitxml=junit.xml # --cov=pymssql --cov-report=xml --cov-report=term-missing markers = slow: Mark a pymssql test as slow (usually taking more one second or more). ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1701662366.604411 pymssql-2.2.11/setup.cfg0000644000000000000000000000022500000000000015064 0ustar00rootroot00000000000000[options] setup_requires = cython>=0.29.22 setuptools>=54.0 setuptools_scm[toml]>=5.0,<7.0 wheel>=0.36.2 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/setup.py0000644000000000000000000002725600000000000014772 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- #!/usr/bin/env python # # setup.py # # Copyright (C) 2009 Damien Churchill # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA # import os from os.path import exists, join, splitext from pathlib import Path import platform import struct import sys from setuptools import setup, Extension from setuptools.command.test import test as TestCommand from distutils import log from distutils.cmd import Command from distutils.command.clean import clean as _clean have_c_files = exists('pymssql/_mssql.c') and exists('pymssql/_pymssql.c') if have_c_files: from distutils.command.build_ext import build_ext as _build_ext else: # Force `setup_requires` stuff like Cython to be installed before proceeding from setuptools.dist import Distribution Distribution(dict(setup_requires='Cython>=0.29.21')) from Cython.Distutils import build_ext as _build_ext def check_env(env_name, default): val = os.getenv(env_name, default) if val.upper() in ('1', 'YES', 'TRUE'): return True elif val.upper() in ('0', 'NO', 'FALSE'): return False else: raise Exception(f"Unsupported environment value {env_name}={val}") LINK_FREETDS_STATICALLY = check_env('LINK_FREETDS_STATICALLY', 'YES') LINK_OPENSSL = check_env('LINK_OPENSSL', 'YES') LINK_KRB5 = check_env('LINK_KRB5', 'YES') # 32 bit or 64 bit system? BITNESS = struct.calcsize("P") * 8 WINDOWS = platform.system() == 'Windows' include_dirs = [] library_dirs = [] libraries = ['sybdb'] prefix = None if os.getenv('PYMSSQL_FREETDS'): prefix = os.path.abspath(os.getenv('PYMSSQL_FREETDS').strip()) elif exists("/usr/local/include/sqlfront.h"): prefix = "/usr/local" elif exists("/usr/local/opt/freetds/include/sqlfront.h"): # brew macOS on Intel prefix = "/usr/local/opt/freetds" elif exists("/opt/homebrew/opt/freetds/include/sqlfront.h"): # brew macOS on Apple Silicon/ARM prefix = "/opt/homebrew/opt/freetds" elif exists("/opt/local/include/sqlfront.h"): # MacPorts prefix = "/opt/local" elif exists("/sw/include/sqlfront.h"): # Fink prefix = "/sw" if prefix: print(f"prefix='{prefix}'") include_dirs = [ join(prefix, "include") ] if BITNESS == 64 and exists(join(prefix, "lib64")): library_dirs = [ join(prefix, "lib64") ] else: library_dirs = [ join(prefix, "lib") ] if os.getenv('PYMSSQL_FREETDS_INCLUDEDIR'): include_dirs = [ os.getenv('PYMSSQL_FREETDS_INCLUDEDIR') ] if os.getenv('PYMSSQL_FREETDS_LIBDIR'): library_dirs = [ os.getenv('PYMSSQL_FREETDS_LIBDIR') ] print("setup.py: platform.system() =>", platform.system()) print("setup.py: platform.architecture() =>", platform.architecture()) if not WINDOWS: print("setup.py: platform.libc_ver() =>", platform.libc_ver()) print("setup.py: include_dirs =>", include_dirs) print("setup.py: library_dirs =>", library_dirs) if not WINDOWS and platform.libc_ver()[0] == 'glibc': # check for clock_gettime, link with librt for glibc<2.17 from dev import ccompiler compiler = ccompiler.new_compiler() if not compiler.has_function('clock_gettime(0,NULL)', includes=['time.h']): if compiler.has_function('clock_gettime(0,NULL)', includes=['time.h'], libraries=['rt']): libraries.append('rt') else: print("setup.py: could not locate 'clock_gettime' function required by FreeTDS.") sys.exit(1) class build_ext(_build_ext): """ Subclass the Cython build_ext command so it: * Can handle different C compilers on Windows * Links in the libraries we collected """ def build_extensions(self): global library_dirs, include_dirs, libraries if WINDOWS: # Detect the compiler so we can specify the correct command line switches # and libraries from distutils.cygwinccompiler import Mingw32CCompiler extra_cc_args = [] if isinstance(self.compiler, Mingw32CCompiler): # Compiler is Mingw32 extra_cc_args = [ '-Wl,-allow-multiple-definition', '-Wl,-subsystem,windows-mthreads', '-mwindows', '-Wl,--strip-all' ] libraries = [ 'libiconv', 'iconv', 'sybdb', 'ws2_32', 'wsock32', 'kernel32', ] else: # Assume compiler is Visual Studio if LINK_FREETDS_STATICALLY: libraries = [ 'replacements', 'db-lib', 'tds', 'tdsutils', 'ws2_32', 'wsock32', 'kernel32', 'shell32', ] if LINK_OPENSSL: libraries.extend([ 'libssl_static', 'libcrypto_static', 'crypt32', 'advapi32', 'gdi32', 'user32', ]) else: libraries = [ 'ct', 'sybdb', 'ws2_32', 'wsock32', 'kernel32', 'shell32', ] if LINK_OPENSSL: libraries.extend(['libssl', 'libcrypto']) for e in self.extensions: e.extra_compile_args.extend(extra_cc_args) e.libraries.extend(libraries) if LINK_OPENSSL: if BITNESS == 32: e.library_dirs.append("c:/Program Files (x86)/OpenSSL-Win32/lib") else: e.library_dirs.append("c:/Program Files/OpenSSL/lib") else: if LINK_KRB5: libraries.extend([ 'gssapi_krb5', 'krb5'] ) if LINK_OPENSSL and LINK_FREETDS_STATICALLY: libraries.extend([ 'ssl', 'crypto' ]) for e in self.extensions: e.libraries.extend(libraries) _build_ext.build_extensions(self) class clean(_clean): """ Subclass clean so it removes all the Cython generated files. """ def run(self): _clean.run(self) for ext in self.distribution.ext_modules: cy_sources = [splitext(s)[0] for s in ext.sources] for cy_source in cy_sources: # .so/.pyd files are created in place when using 'develop' for ext in ('.c', '.so', '.pyd'): generated = cy_source + ext if exists(generated): log.info('removing %s', generated) os.remove(generated) class release(Command): """ Setuptools command to run all the required commands to perform a release. This acts differently depending on the platform it is being run on. """ description = "Run all the commands required for a release." user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): if WINDOWS: self.release_windows() else: self.release_unix() def release_windows(self): # generate windows source distributions sdist = self.distribution.get_command_obj('sdist') sdist.formats = 'zip' sdist.ensure_finalized() sdist.run() # generate a windows egg self.run_command('bdist_egg') # generate windows installers bdist = self.reinitialize_command('bdist') bdist.formats = 'zip,wininst' bdist.ensure_finalized() bdist.run() def release_unix(self): # generate linux source distributions sdist = self.distribution.get_command_obj('sdist') sdist.formats = 'gztar,bztar' sdist.ensure_finalized() sdist.run() def ext_modules(): if have_c_files: source_extension = 'c' else: source_extension = 'pyx' ext_modules = [ Extension('pymssql._mssql', [join('src', 'pymssql', '_mssql.%s' % source_extension)], extra_compile_args = [ '-DMSDBLIB' ], include_dirs = include_dirs, library_dirs = library_dirs, ), Extension('pymssql._pymssql', [join('src', 'pymssql', '_pymssql.%s' % source_extension)], extra_compile_args = [ '-DMSDBLIB' ], include_dirs = include_dirs, library_dirs = library_dirs, ), ] for e in ext_modules: e.cython_directives = {'language_level': sys.version_info[0]} return ext_modules def mk_long_description(numrev=1): readme = (Path('__file__').parent / 'README.rst').read_text() chlog = Path('__file__').parent / 'ChangeLog.rst' lines = [] with chlog.open('r') as f: count = 0 l = f.readline() while l: if l.startswith('Version 2'): count += 1 if count > numrev: break lines.append(l) l = f.readline() return readme + "\n\n" + ''.join(lines).strip() setup( name = 'pymssql', use_scm_version = { "write_to": "src/pymssql/version.h", "write_to_template": '#define PYMSSQL_VERSION "{version}"', "local_scheme": "no-local-version", }, description = 'DB-API interface to Microsoft SQL Server for Python. (new Cython-based version)', long_description = mk_long_description(2), long_description_content_type = 'text/x-rst', author = 'Damien Churchill', author_email = 'damoxc@gmail.com', maintainer = 'Mikhail Terekhov', maintainer_email = 'termim@gmail.com', license = 'LGPL', platforms = 'any', keywords = ['mssql', 'SQL Server', 'database', 'DB-API'], project_urls={ "Documentation": "http://pymssql.readthedocs.io", "Source": "https://github.com/pymssql/pymssql", "Changelog": "https://github.com/pymssql/pymssql/blob/master/ChangeLog.rst", }, cmdclass = { 'build_ext': build_ext, 'clean': clean, 'release': release, }, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Database", "Topic :: Database :: Database Engines/Servers", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Operating System :: Unix", ], zip_safe = False, setup_requires=['setuptools_scm[toml]>=5.0,<7.0', 'Cython>=0.29.22'], tests_require=['psutil<5.9.5', 'pytest', 'pytest-timeout'], ext_modules = ext_modules(), packages = [ 'pymssql'], package_dir = {'': 'src'}, ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5764108 pymssql-2.2.11/src/0000755000000000000000000000000000000000000014033 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.5964108 pymssql-2.2.11/src/pymssql/0000755000000000000000000000000000000000000015543 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/src/pymssql/__init__.py0000644000000000000000000000014500000000000017654 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- from ._pymssql import * from ._pymssql import __version__, __full_version__ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/src/pymssql/_mssql.pxd0000644000000000000000000000404200000000000017556 0ustar00rootroot00000000000000from cpython cimport bool from .sqlfront cimport DBPROCESS, BYTE cdef void log(char *, ...) cdef struct _mssql_parameter_node: _mssql_parameter_node *next BYTE *value cdef class MSSQLConnection: # class property variables cdef bint _connected cdef int _rows_affected cdef int _query_timeout cdef char *_charset # class internal variables cdef DBPROCESS *dbproc cdef int last_msg_no cdef int last_msg_severity cdef int last_msg_state cdef int last_msg_line cdef int last_dbresults cdef int num_columns cdef public bint debug_queries cdef char *last_msg_str cdef char *last_msg_srv cdef char *last_msg_proc cdef tuple column_names cdef tuple column_types cdef object msghandler cpdef cancel(self) cdef void clear_metadata(self) cdef object convert_db_value(self, BYTE *, int, int) cdef int convert_python_value(self, object value, BYTE **, int*, int*) except -1 cpdef execute_query(self, query, params=?) cpdef execute_non_query(self, query, params=?) cpdef execute_row(self, query, params=?) cpdef execute_scalar(self, query, params=?) cdef fetch_next_row(self, int, int) cdef format_and_run_query(self, query_string, params=?) cdef format_sql_command(self, format, params=?) cdef get_result(self) cdef get_row(self, int, int) cpdef set_msghandler(self, object handler) cdef bcp_init(self, object) cdef bcp_hint(self, BYTE * value, int valuelen) cdef bcp_bind(self, object value, int is_none, int column_db_type, int position, BYTE **data) cdef bcp_batch(self) cpdef bcp_sendrow(self, object element, object column_ids) cdef bcp_done(self) cdef class MSSQLRowIterator: cdef MSSQLConnection conn cdef int row_format cdef class MSSQLStoredProcedure: cdef MSSQLConnection conn cdef DBPROCESS *dbproc cdef char *procname cdef int param_count cdef bool had_positional cdef list output_indexes cdef dict params cdef _mssql_parameter_node *params_list ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/src/pymssql/_mssql.pyx0000644000000000000000000022436600000000000017620 0ustar00rootroot00000000000000""" This is an effort to convert the pymssql low-level C module to Cython. """ # # _mssql.pyx # # Copyright (C) 2003 Joon-cheol Park # 2008 Andrzej Kukula # 2009-2010 Damien Churchill # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA # DEF PYMSSQL_DEBUG = 0 DEF PYMSSQL_DEBUG_ERRORS = 0 DEF PYMSSQL_CHARSETBUFSIZE = 100 DEF MSSQLDB_MSGSIZE = 1024 DEF PYMSSQL_MSGSIZE = (MSSQLDB_MSGSIZE * 8) DEF EXCOMM = 9 # Provide constants missing in FreeTDS 0.82 so that we can build against it DEF DBVERSION_71 = 5 DEF DBVERSION_72 = 6 # Provide constant missing from FreeTDS 0.91 so that we can build against it DEF DBVERSION_73 = 7 ROW_FORMAT_TUPLE = 1 ROW_FORMAT_DICT = 2 cdef int _ROW_FORMAT_TUPLE = ROW_FORMAT_TUPLE cdef int _ROW_FORMAT_DICT = ROW_FORMAT_DICT from collections.abc import Iterable import os import sys import socket import decimal import binascii import datetime import re import uuid from .sqlfront cimport * from libc.stdio cimport fprintf, snprintf, stderr, FILE from libc.string cimport strlen, strncpy, memcpy, memset from cpython cimport bool from cpython.mem cimport PyMem_Malloc, PyMem_Free from cpython.long cimport PY_LONG_LONG from cpython.ref cimport Py_INCREF from cpython.tuple cimport PyTuple_New, PyTuple_SetItem cdef extern from "version.h": const char *PYMSSQL_VERSION # Vars to store messages from the server in cdef int _mssql_last_msg_no = 0 cdef int _mssql_last_msg_severity = 0 cdef int _mssql_last_msg_state = 0 cdef int _mssql_last_msg_line = 0 cdef char *_mssql_last_msg_str = PyMem_Malloc(PYMSSQL_MSGSIZE) _mssql_last_msg_str[0] = 0 cdef char *_mssql_last_msg_srv = PyMem_Malloc(PYMSSQL_MSGSIZE) _mssql_last_msg_srv[0] = 0 cdef char *_mssql_last_msg_proc = PyMem_Malloc(PYMSSQL_MSGSIZE) _mssql_last_msg_proc[0] = 0 IF PYMSSQL_DEBUG == 1: cdef int _row_count = 0 cdef bytes HOSTNAME = socket.gethostname().encode('utf-8') # List to store the connection objects in cdef list connection_object_list = list() # Store the 32bit int limit values cdef int MAX_INT = 2147483647 cdef int MIN_INT = -2147483648 # Store the module version __full_version__ = PYMSSQL_VERSION.decode('ascii') __version__ = '.'.join(__full_version__.split('.')[:3]) VERSION = tuple(int(c) if c.isdigit() else c for c in __full_version__.split('.')[:3]) ############################# ## DB-API type definitions ## ############################# STRING = 1 BINARY = 2 NUMBER = 3 DATETIME = 4 DECIMAL = 5 ################## ## DB-LIB types ## ################## SQLBINARY = SYBBINARY SQLBIT = SYBBIT SQLBITN = 104 SQLCHAR = SYBCHAR SQLDATETIME = SYBDATETIME SQLDATETIM4 = SYBDATETIME4 SQLDATETIMN = SYBDATETIMN SQLDECIMAL = SYBDECIMAL SQLFLT4 = SYBREAL SQLFLT8 = SYBFLT8 SQLFLTN = SYBFLTN SQLIMAGE = SYBIMAGE SQLINT1 = SYBINT1 SQLINT2 = SYBINT2 SQLINT4 = SYBINT4 SQLINT8 = SYBINT8 SQLINTN = SYBINTN SQLMONEY = SYBMONEY SQLMONEY4 = SYBMONEY4 SQLMONEYN = SYBMONEYN SQLNUMERIC = SYBNUMERIC SQLREAL = SYBREAL SQLTEXT = SYBTEXT SQLVARBINARY = SYBVARBINARY SQLVARCHAR = SYBVARCHAR SQLUUID = 36 SQLDATE = 40 SQLTIME = 41 SQLDATETIME2 = 42 #################### ## TDS_ENCRYPTION_LEVEL ## #################### cdef dict TDS_ENCRYPTION_LEVEL = { 'default': 0, # TDS_ENCRYPTION_DEFAULT, 'off': 1, # TDS_ENCRYPTION_OFF, 'request': 2, # TDS_ENCRYPTION_REQUEST, 'require': 3 # TDS_ENCRYPTION_REQUIRE } ################### ## Type mappings ## ################### cdef dict DBTYPES = { 'bool': SQLBITN, 'str': SQLVARCHAR, 'unicode': SQLVARCHAR, 'Decimal': SQLDECIMAL, 'datetime': SQLDATETIME, 'date': SQLDATETIME, 'float': SQLFLT8, 'bytes': SQLVARBINARY, 'bytearray': SQLVARBINARY, #Dump type for work vith None 'NoneType': SQLVARCHAR, } cpdef int py2db_type(py_type, value) except -1: try: type_name = py_type.__name__ if type_name == 'int': if value is not None and value >= -2147483648 and value <= 2147483647: # -2^31 - 2^31-1 return SQLINTN else: return SQLINT8 return DBTYPES[type_name] except (AttributeError, KeyError): raise MSSQLDriverException('Unable to determine database type from python %s type' % type_name) ####################### ## Exception classes ## ####################### cdef class MSSQLException(Exception): """ Base exception class for the MSSQL driver. """ cdef class MSSQLDriverException(MSSQLException): """ Inherits from the base class and raised when an error is caused within the driver itself. """ cdef class MSSQLDatabaseException(MSSQLException): """ Raised when an error occurs within the database. """ cdef readonly int number cdef readonly int severity cdef readonly int state cdef readonly int line cdef readonly char *text cdef readonly char *srvname cdef readonly char *procname property message: def __get__(self): if self.procname: return 'SQL Server message %d, severity %d, state %d, ' \ 'procedure %s, line %d:\n%s' % (self.number, self.severity, self.state, self.procname, self.line, self.text) else: return 'SQL Server message %d, severity %d, state %d, ' \ 'line %d:\n%s' % (self.number, self.severity, self.state, self.line, self.text) # Module attributes for configuring _mssql login_timeout = 60 min_error_severity = 6 wait_callback = None def set_wait_callback(a_callable): global wait_callback wait_callback = a_callable # Buffer size for large numbers DEF NUMERIC_BUF_SZ = 45 cdef bytes ensure_bytes(s, encoding='utf-8'): try: decoded = s.decode(encoding) return decoded.encode(encoding) except AttributeError: return s.encode(encoding) cdef void log(char * message, ...): if PYMSSQL_DEBUG == 1: fprintf(stderr, "+++ %s\n", message) ################### ## Error Handler ## ################### cdef int err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) noexcept with gil: cdef char *mssql_lastmsgstr cdef int *mssql_lastmsgno cdef int *mssql_lastmsgseverity cdef int *mssql_lastmsgstate cdef int _min_error_severity = min_error_severity cdef char mssql_message[PYMSSQL_MSGSIZE] if severity < _min_error_severity: return INT_CANCEL if dberrstr == NULL: dberrstr = '' if oserrstr == NULL: oserrstr = '' IF PYMSSQL_DEBUG == 1 or PYMSSQL_DEBUG_ERRORS == 1: fprintf(stderr, "\n*** err_handler(dbproc = %p, severity = %d, " \ "dberr = %d, oserr = %d, dberrstr = '%s', oserrstr = '%s'); " \ "DBDEAD(dbproc) = %d\n", dbproc, severity, dberr, oserr, dberrstr, oserrstr, DBDEAD(dbproc)); fprintf(stderr, "*** previous max severity = %d\n\n", _mssql_last_msg_severity); mssql_lastmsgstr = _mssql_last_msg_str mssql_lastmsgno = &_mssql_last_msg_no mssql_lastmsgseverity = &_mssql_last_msg_severity mssql_lastmsgstate = &_mssql_last_msg_state for conn in connection_object_list: if dbproc != (conn).dbproc: continue mssql_lastmsgstr = (conn).last_msg_str mssql_lastmsgno = &(conn).last_msg_no mssql_lastmsgseverity = &(conn).last_msg_severity mssql_lastmsgstate = &(conn).last_msg_state if DBDEAD(dbproc): log("+++ err_handler: dbproc is dead; killing conn...\n") conn.mark_disconnected() break if severity > mssql_lastmsgseverity[0]: mssql_lastmsgseverity[0] = severity mssql_lastmsgno[0] = dberr mssql_lastmsgstate[0] = oserr if oserr != DBNOERR and oserr != 0: if severity == EXCOMM: snprintf( mssql_message, sizeof(mssql_message), '%sDB-Lib error message %d, severity %d:\n%s\nNet-Lib error during %s (%d)\n', mssql_lastmsgstr, dberr, severity, dberrstr, oserrstr, oserr) else: snprintf( mssql_message, sizeof(mssql_message), '%sDB-Lib error message %d, severity %d:\n%s\nOperating System error during %s (%d)\n', mssql_lastmsgstr, dberr, severity, dberrstr, oserrstr, oserr) else: snprintf( mssql_message, sizeof(mssql_message), '%sDB-Lib error message %d, severity %d:\n%s\n', mssql_lastmsgstr, dberr, severity, dberrstr) strncpy(mssql_lastmsgstr, mssql_message, PYMSSQL_MSGSIZE) mssql_lastmsgstr[ PYMSSQL_MSGSIZE - 1 ] = b'\0' return INT_CANCEL ##################### ## Message Handler ## ##################### cdef int msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, LINE_T line) noexcept with gil: cdef int *mssql_lastmsgno cdef int *mssql_lastmsgseverity cdef int *mssql_lastmsgstate cdef int *mssql_lastmsgline cdef char *mssql_lastmsgstr cdef char *mssql_lastmsgsrv cdef char *mssql_lastmsgproc cdef int _min_error_severity = min_error_severity cdef MSSQLConnection conn = None IF PYMSSQL_DEBUG == 1: fprintf(stderr, "\n+++ msg_handler(dbproc = %p, msgno = %d, " \ "msgstate = %d, severity = %d, msgtext = '%s', " \ "srvname = '%s', procname = '%s', line = %d)\n", dbproc, msgno, msgstate, severity, msgtext, srvname, procname, line); fprintf(stderr, "+++ previous max severity = %d\n\n", _mssql_last_msg_severity); for cnx in connection_object_list: if (cnx).dbproc != dbproc: continue conn = cnx break if conn is not None and conn.msghandler is not None: conn.msghandler(msgstate, severity, srvname, procname, line, msgtext) if severity < _min_error_severity: return INT_CANCEL if conn is not None: mssql_lastmsgstr = conn.last_msg_str mssql_lastmsgsrv = conn.last_msg_srv mssql_lastmsgproc = conn.last_msg_proc mssql_lastmsgno = &conn.last_msg_no mssql_lastmsgseverity = &conn.last_msg_severity mssql_lastmsgstate = &conn.last_msg_state mssql_lastmsgline = &conn.last_msg_line else: mssql_lastmsgstr = _mssql_last_msg_str mssql_lastmsgsrv = _mssql_last_msg_srv mssql_lastmsgproc = _mssql_last_msg_proc mssql_lastmsgno = &_mssql_last_msg_no mssql_lastmsgseverity = &_mssql_last_msg_severity mssql_lastmsgstate = &_mssql_last_msg_state mssql_lastmsgline = &_mssql_last_msg_line # Calculate the maximum severity of all messages in a row # Fill the remaining fields as this is going to raise the exception if severity > mssql_lastmsgseverity[0]: mssql_lastmsgseverity[0] = severity mssql_lastmsgno[0] = msgno mssql_lastmsgstate[0] = msgstate mssql_lastmsgline[0] = line strncpy(mssql_lastmsgstr, msgtext, PYMSSQL_MSGSIZE) strncpy(mssql_lastmsgsrv, srvname, PYMSSQL_MSGSIZE) strncpy(mssql_lastmsgproc, procname, PYMSSQL_MSGSIZE) return 0 cdef int db_sqlexec(DBPROCESS *dbproc): cdef RETCODE rtc # The dbsqlsend function sends Transact-SQL statements, stored in the # command buffer of the DBPROCESS, to SQL Server. # # It does not wait for a response. This gives us an opportunity to do other # things while waiting for the server response. # # After dbsqlsend returns SUCCEED, dbsqlok must be called to verify the # accuracy of the command batch. Then dbresults can be called to process # the results. with nogil: rtc = dbsqlsend(dbproc) if rtc != SUCCEED: return rtc # If we've reached here, dbsqlsend didn't fail so the query is in progress. # Wait for results to come back and return the return code, optionally # calling wait_callback first... return db_sqlok(dbproc) cdef int db_sqlok(DBPROCESS *dbproc): cdef RETCODE rtc # If there is a wait callback, call it with the file descriptor we're # waiting on. # The wait_callback is a good place to do things like yield to another # gevent greenlet -- e.g.: gevent.socket.wait_read(read_fileno) if wait_callback: read_fileno = dbiordesc(dbproc) wait_callback(read_fileno) # dbsqlok following dbsqlsend is the equivalent of dbsqlexec. This function # must be called after dbsqlsend returns SUCCEED. When dbsqlok returns, # then dbresults can be called to process the results. with nogil: rtc = dbsqlok(dbproc) return rtc cdef void clr_err(MSSQLConnection conn): if conn != None: conn.last_msg_no = 0 conn.last_msg_severity = 0 conn.last_msg_state = 0 conn.last_msg_str[0] = 0 else: _mssql_last_msg_no = 0 _mssql_last_msg_severity = 0 _mssql_last_msg_state = 0 _mssql_last_msg_str[0] = 0 cdef RETCODE db_cancel(MSSQLConnection conn): cdef RETCODE rtc if conn == None: return SUCCEED if conn.dbproc == NULL: return SUCCEED with nogil: rtc = dbcancel(conn.dbproc); conn.clear_metadata() return rtc ############################## ## MSSQL Row Iterator Class ## ############################## cdef class MSSQLRowIterator: def __init__(self, connection, int row_format): self.conn = connection self.row_format = row_format def __iter__(self): return self def __next__(self): assert_connected(self.conn) clr_err(self.conn) return self.conn.fetch_next_row(1, self.row_format) ############################ ## MSSQL Connection Class ## ############################ cdef class MSSQLConnection: property charset: """ The current encoding in use. """ def __get__(self): if strlen(self._charset): return self._charset.decode('ascii') return None property connected: """ True if the connection to a database is open. """ def __get__(self): return self._connected property identity: """ Returns identity value of the last inserted row. If the previous operation did not involve inserting a row into a table with an identity column, None is returned. ** Usage ** >>> conn.execute_non_query("INSERT INTO table (name) VALUES ('John')") >>> print 'Last inserted row has ID = %s' % conn.identity Last inserted row has ID = 178 """ def __get__(self): return self.execute_scalar('SELECT SCOPE_IDENTITY()') property query_timeout: """ A """ def __get__(self): return self._query_timeout def __set__(self, value): cdef int val = int(value) cdef RETCODE rtc if val < 0: raise ValueError("The 'query_timeout' attribute must be >= 0.") # XXX: Currently this will set it application wide :-( rtc = dbsettime(val) check_and_raise(rtc, self) # if all is fine then set our attribute self._query_timeout = val property rows_affected: """ Number of rows affected by last query. For SELECT statements this value is only meaningful after reading all rows. """ def __get__(self): return self._rows_affected property tds_version: """ Returns what TDS version the connection is using. """ def __get__(self): cdef int version = dbtds(self.dbproc) if version == 12: return 7.4 elif version == 11: return 7.3 elif version == 10: return 7.2 elif version == 9: return 7.1 elif version == 8: return 7.0 elif version == 6: return 5.0 elif version == 4: return 4.2 return None property tds_version_tuple: """ Reports what TDS version the connection is using in tuple form which is more easily handled (parse, compare) programmatically. If no TDS version can be detected the value is None. """ def __get__(self): cdef int version = dbtds(self.dbproc) if version == 12: return (7, 4) elif version == 11: return (7, 3) elif version == 10: return (7, 2) elif version == 9: return (7, 1) elif version == 8: return (7, 0) elif version == 6: return (5, 0) elif version == 4: return (4, 2) return None def __cinit__(self): log("_mssql.MSSQLConnection.__cinit__()") self._connected = 0 self._charset = PyMem_Malloc(PYMSSQL_CHARSETBUFSIZE) self._charset[0] = 0 self.last_msg_str = PyMem_Malloc(PYMSSQL_MSGSIZE) self.last_msg_str[0] = 0 self.last_msg_srv = PyMem_Malloc(PYMSSQL_MSGSIZE) self.last_msg_srv[0] = 0 self.last_msg_proc = PyMem_Malloc(PYMSSQL_MSGSIZE) self.last_msg_proc[0] = 0 self.column_names = None self.column_types = None def __init__(self, server="localhost", user=None, password=None, charset='UTF-8', database='', appname=None, port='1433', tds_version=None, encryption=None, conn_properties=None): log("_mssql.MSSQLConnection.__init__()") cdef LOGINREC *login cdef RETCODE rtc cdef char *_charset # support MS methods of connecting locally instance = "" if "\\" in server: server, instance = server.split("\\") if server in (".", "(local)"): server = "localhost" server = server + "\\" + instance if instance else server login = dblogin() if login == NULL: raise MSSQLDriverException("dblogin() failed") appname = appname or "pymssql=%s" % __full_version__ # For Python 3, we need to convert unicode to byte strings cdef bytes user_bytes cdef char *user_cstr = NULL if user is not None: user_bytes = user.encode('utf-8') user_cstr = user_bytes cdef bytes password_bytes cdef char *password_cstr = NULL if password is not None: password_bytes = password.encode('utf-8') password_cstr = password_bytes cdef bytes appname_bytes = appname.encode('utf-8') cdef char *appname_cstr = appname_bytes if user is not None: DBSETLUSER(login, user_cstr) if password is not None: DBSETLPWD(login, password_cstr) DBSETLAPP(login, appname_cstr) if tds_version is not None: DBSETLVERSION(login, _tds_ver_str_to_constant(tds_version)) if encryption is not None: if encryption in TDS_ENCRYPTION_LEVEL: DBSETLENCRYPT(login, TDS_ENCRYPTION_LEVEL[encryption]) else: raise ValueError(f"'encryption' option should be {TDS_ENCRYPTION_LEVEL.keys())} or None.") # add the port to the server string if it doesn't have one already and # if we are not using an instance if ':' not in server and not instance: server = '%s:%s' % (server, port) # override the HOST to be the portion without the server, otherwise # FreeTDS chokes when server still has the port definition. # BUT, a patch on the mailing list fixes the need for this. I am # leaving it here just to remind us how to fix the problem if the bug # doesn't get fixed for a while. But if it does get fixed, this code # can be deleted. # patch: http://lists.ibiblio.org/pipermail/freetds/2011q2/026997.html #if ':' in server: # os.environ['TDSHOST'] = server.split(':', 1)[0] #else: # os.environ['TDSHOST'] = server # Add ourselves to the global connection list connection_object_list.append(self) cdef bytes charset_bytes # Set the character set name if charset: charset_bytes = charset.encode('utf-8') _charset = charset_bytes strncpy(self._charset, _charset, PYMSSQL_CHARSETBUFSIZE) DBSETLCHARSET(login, self._charset) # For Python 3, we need to convert unicode to byte strings cdef bytes dbname_bytes cdef char *dbname_cstr # Put the DB name in the login LOGINREC because it helps with connections to Azure if database: dbname_bytes = database.encode('utf-8') dbname_cstr = dbname_bytes DBSETLDBNAME(login, dbname_cstr) # Set the login timeout # XXX: Currently this will set it application wide :-( dbsetlogintime(login_timeout) cdef bytes server_bytes = server.encode('utf-8') cdef char *server_cstr = server_bytes # Connect to the server with nogil: self.dbproc = dbopen(login, server_cstr) # Frees the login record, can be called immediately after dbopen. dbloginfree(login) if self.dbproc == NULL: log("_mssql.MSSQLConnection.__init__() -> dbopen() returned NULL") if self in connection_object_list: connection_object_list.remove(self) maybe_raise_MSSQLDatabaseException(None) raise MSSQLDriverException("Connection to the database failed for an unknown reason.") self._connected = 1 if conn_properties is None: conn_properties = \ "SET ARITHABORT ON;" \ "SET CONCAT_NULL_YIELDS_NULL ON;" \ "SET ANSI_NULLS ON;" \ "SET ANSI_NULL_DFLT_ON ON;" \ "SET ANSI_PADDING ON;" \ "SET ANSI_WARNINGS ON;" \ "SET ANSI_NULL_DFLT_ON ON;" \ "SET CURSOR_CLOSE_ON_COMMIT ON;" \ "SET QUOTED_IDENTIFIER ON;" \ "SET TEXTSIZE 2147483647;" # http://msdn.microsoft.com/en-us/library/aa259190%28v=sql.80%29.aspx elif isinstance(conn_properties, Iterable) and not isinstance(conn_properties, str): conn_properties = ' '.join(conn_properties) cdef bytes conn_props_bytes cdef char *conn_props_cstr if conn_properties: log("_mssql.MSSQLConnection.__init__() -> dbcmd() setting connection values") # Set connection properties, some reasonable values are used by # default but they can be customized conn_props_bytes = conn_properties.encode(charset) conn_props_cstr = conn_props_bytes dbcmd(self.dbproc, conn_props_bytes) rtc = db_sqlexec(self.dbproc) if (rtc == FAIL): raise MSSQLDriverException("Could not set connection properties") db_cancel(self) clr_err(self) if database: self.select_db(database) def __dealloc__(self): log("_mssql.MSSQLConnection.__dealloc__()") self.close() PyMem_Free(self._charset) PyMem_Free(self.last_msg_str) PyMem_Free(self.last_msg_srv) PyMem_Free(self.last_msg_proc) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def __iter__(self): assert_connected(self) clr_err(self) return MSSQLRowIterator(self, ROW_FORMAT_DICT) cpdef set_msghandler(self, object handler): """ set_msghandler(handler) -- set the msghandler for the connection This function allows setting a msghandler for the connection to allow a client to gain access to the messages returned from the server. """ self.msghandler = handler cpdef cancel(self): """ cancel() -- cancel all pending results. This function cancels all pending results from the last SQL operation. It can be called more than once in a row. No exception is raised in this case. """ log("_mssql.MSSQLConnection.cancel()") cdef RETCODE rtc assert_connected(self) clr_err(self) rtc = db_cancel(self) check_and_raise(rtc, self) cdef void clear_metadata(self): log("_mssql.MSSQLConnection.clear_metadata()") self.column_names = None self.column_types = None self.num_columns = 0 self.last_dbresults = 0 def close(self): """ close() -- close connection to an MS SQL Server. This function tries to close the connection. It can be called more than once in a row. No exception is raised in this case. """ log("_mssql.MSSQLConnection.close()") if self == None: return None if not self._connected: return None clr_err(self) with nogil: dbclose(self.dbproc) self.mark_disconnected() def mark_disconnected(self): log("_mssql.MSSQLConnection.mark_disconnected()") self.dbproc = NULL self._connected = 0 connection_object_list.remove(self) cdef object convert_db_value(self, BYTE *data, int dbtype, int length): log("_mssql.MSSQLConnection.convert_db_value()") cdef char buf[NUMERIC_BUF_SZ] # buffer in which we store text rep of bug nums cdef int converted_length cdef long prevPrecision cdef BYTE precision cdef DBDATEREC di cdef DBDATETIME dt cdef DBCOL dbcol IF PYMSSQL_DEBUG == 1: sys.stderr.write("convert_db_value: dbtype = %d; length = %d\n" % (dbtype, length)) if dbtype == SQLBIT: return bool((data)[0]) elif dbtype == SQLINT1: return int((data)[0]) elif dbtype == SQLINT2: return int((data)[0]) elif dbtype == SQLINT4: return int((data)[0]) elif dbtype == SQLINT8: return long((data)[0]) elif dbtype == SQLFLT4: return float((data)[0]) elif dbtype == SQLFLT8: return float((data)[0]) elif dbtype in (SQLMONEY, SQLMONEY4, SQLNUMERIC, SQLDECIMAL): dbcol.SizeOfStruct = sizeof(dbcol) if dbtype in (SQLMONEY, SQLMONEY4): precision = 4 else: precision = 0 converted_length = dbconvert(self.dbproc, dbtype, data, -1, SQLCHAR, buf, NUMERIC_BUF_SZ) with decimal.localcontext() as ctx: # Python 3 doesn't like decimal.localcontext() with prec == 0 ctx.prec = precision if precision > 0 else 1 return decimal.Decimal(_remove_locale(buf, converted_length).decode(self._charset)) elif dbtype in (SQLDATETIM4, SQLDATETIME2): dbconvert(self.dbproc, dbtype, data, -1, SQLDATETIME, &dt, -1) dbdatecrack(self.dbproc, &di, &dt) return datetime.datetime(di.year, di.month, di.day, di.hour, di.minute, di.second, di.millisecond * 1000) elif dbtype == SQLDATE: dbconvert(self.dbproc, dbtype, data, -1, SQLDATETIME, &dt, -1) dbdatecrack(self.dbproc, &di, &dt) return datetime.date(di.year, di.month, di.day) elif dbtype == SQLTIME: dbconvert(self.dbproc, dbtype, data, -1, SQLDATETIME, &dt, -1) dbdatecrack(self.dbproc, &di, &dt) return datetime.time(di.hour, di.minute, di.second, di.millisecond * 1000) elif dbtype == SQLDATETIME: dbdatecrack(self.dbproc, &di, data) return datetime.datetime(di.year, di.month, di.day, di.hour, di.minute, di.second, di.millisecond * 1000) elif dbtype in (SQLVARCHAR, SQLCHAR, SQLTEXT): if strlen(self._charset): return (data)[:length].decode(self._charset) else: return (data)[:length] elif dbtype == SQLUUID: return uuid.UUID(bytes_le=(data)[:length]) else: return (data)[:length] cdef int convert_python_value(self, object value, BYTE **dbValue, int *dbtype, int *length) except -1: log("_mssql.MSSQLConnection.convert_python_value()") cdef int *intValue cdef double *dblValue cdef float *fltValue cdef PY_LONG_LONG *longValue cdef char *strValue cdef char *tmp cdef BYTE *binValue cdef DBTYPEINFO decimal_type_info IF PYMSSQL_DEBUG == 1: sys.stderr.write("convert_python_value: value = %r; dbtype = %d" % (value, dbtype[0])) if value is None: dbValue[0] = NULL return 0 if dbtype[0] in (SQLBIT, SQLBITN): intValue = PyMem_Malloc(sizeof(int)) intValue[0] = value dbValue[0] = intValue return 0 if dbtype[0] == SQLINTN: dbtype[0] = SQLINT4 if dbtype[0] in (SQLINT1, SQLINT2, SQLINT4): if value > MAX_INT: raise MSSQLDriverException('value cannot be larger than %d' % MAX_INT) elif value < MIN_INT: raise MSSQLDriverException('value cannot be smaller than %d' % MIN_INT) intValue = PyMem_Malloc(sizeof(int)) intValue[0] = value if dbtype[0] == SQLINT1: dbValue[0] = intValue return 0 if dbtype[0] == SQLINT2: dbValue[0] = intValue return 0 if dbtype[0] == SQLINT4: dbValue[0] = intValue return 0 if dbtype[0] == SQLINT8: longValue = PyMem_Malloc(sizeof(PY_LONG_LONG)) longValue[0] = value dbValue[0] = longValue return 0 if dbtype[0] in (SQLFLT4, SQLREAL): fltValue = PyMem_Malloc(sizeof(float)) fltValue[0] = value dbValue[0] = fltValue return 0 if dbtype[0] == SQLFLT8: dblValue = PyMem_Malloc(sizeof(double)) dblValue[0] = value dbValue[0] = dblValue return 0 if dbtype[0] == SQLDATE: if not isinstance(value, datetime.date): raise TypeError('value can only be a datetime.date') value = value.strftime('%4Y-%m-%d').encode(self.charset) dbtype[0] = SQLCHAR if dbtype[0] in (SQLDATETIM4, SQLDATETIME): if type(value) not in (datetime.date, datetime.datetime): raise TypeError('value can only be a date or datetime') microseconds=0 if type(value) in (datetime.datetime,): microseconds=value.microsecond // 1000 value = value.strftime('%4Y-%m-%d %H:%M:%S.') + \ "%03d" % (microseconds) value = value.encode(self.charset) dbtype[0] = SQLCHAR if dbtype[0] in (SQLNUMERIC, SQLDECIMAL): # There seems to be no harm in setting precision higher than # necessary decimal_type_info.precision = 33 # Figure out `scale` - number of digits after decimal point decimal_type_info.scale = abs(value.as_tuple().exponent) # Need this to prevent Cython error: # "Obtaining 'BYTE *' from temporary Python value" # bytes_value = bytes(str(value), encoding="ascii") bytes_value = unicode(value).encode("ascii") decValue = PyMem_Malloc(sizeof(DBDECIMAL)) length[0] = dbconvert_ps( self.dbproc, SQLCHAR, bytes_value, -1, dbtype[0], decValue, sizeof(DBDECIMAL), &decimal_type_info, ) dbValue[0] = decValue IF PYMSSQL_DEBUG == 1: fprintf(stderr, "convert_python_value: Converted value to DBDECIMAL with length = %d\n", length[0]) for i in range(0, 35): fprintf(stderr, "convert_python_value: dbValue[0][%d] = %d\n", i, dbValue[0][i]) return 0 if dbtype[0] in (SQLMONEY, SQLMONEY4, SQLNUMERIC, SQLDECIMAL): if type(value) in (int, long, bytes): value = decimal.Decimal(value) if type(value) not in (decimal.Decimal, float): raise TypeError('value can only be a Decimal') value = str(value) dbtype[0] = SQLCHAR if dbtype[0] in (SQLVARCHAR, SQLCHAR, SQLTEXT): if not hasattr(value, 'startswith'): raise TypeError('value must be a string type') if strlen(self._charset) > 0 and type(value) is unicode: value = value.encode(self.charset) strValue = PyMem_Malloc(len(value) + 1) tmp = value strncpy(strValue, tmp, len(value) + 1) strValue[ len(value) ] = b'\0'; dbValue[0] = strValue return 0 if dbtype[0] in (SQLBINARY, SQLVARBINARY, SQLIMAGE): if not isinstance(value, (bytes,bytearray)): raise TypeError('value can only be bytes or bytearray') binValue = PyMem_Malloc(len(value)) memcpy(binValue, value, len(value)) length[0] = len(value) dbValue[0] = binValue return 0 if dbtype[0] == SQLUUID: binValue = PyMem_Malloc(16) memcpy(binValue, value.bytes_le, 16) length[0] = 16 dbValue[0] = binValue return 0 # No conversion was possible so raise an error raise MSSQLDriverException('Unable to convert value') cpdef execute_non_query(self, query_string, params=None): """ execute_non_query(query_string, params=None) This method sends a query to the MS SQL Server to which this object instance is connected. After completion, its results (if any) are discarded. An exception is raised on failure. If there are any pending results or rows prior to executing this command, they are silently discarded. This method accepts Python formatting. Please see execute_query() for more details. This method is useful for INSERT, UPDATE, DELETE and for Data Definition Language commands, i.e. when you need to alter your database schema. After calling this method, rows_affected property contains number of rows affected by the last SQL command. """ log("_mssql.MSSQLConnection.execute_non_query() BEGIN") cdef RETCODE rtc self.format_and_run_query(query_string, params) with nogil: dbresults(self.dbproc) self._rows_affected = dbcount(self.dbproc) rtc = db_cancel(self) check_and_raise(rtc, self) log("_mssql.MSSQLConnection.execute_non_query() END") cpdef execute_query(self, query_string, params=None): """ execute_query(query_string, params=None) This method sends a query to the MS SQL Server to which this object instance is connected. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. After calling this method you may iterate over the connection object to get rows returned by the query. You can use Python formatting here and all values get properly quoted: conn.execute_query('SELECT * FROM empl WHERE id=%d', 13) conn.execute_query('SELECT * FROM empl WHERE id IN %s', ((5,6),)) conn.execute_query('SELECT * FROM empl WHERE name=%s', 'John Doe') conn.execute_query('SELECT * FROM empl WHERE name LIKE %s', 'J%') conn.execute_query('SELECT * FROM empl WHERE name=%(name)s AND \ city=%(city)s', { 'name': 'John Doe', 'city': 'Nowhere' } ) conn.execute_query('SELECT * FROM cust WHERE salesrep=%s \ AND id IN (%s)', ('John Doe', (1,2,3))) conn.execute_query('SELECT * FROM empl WHERE id IN %s',\ (tuple(xrange(4)),)) conn.execute_query('SELECT * FROM empl WHERE id IN %s',\ (tuple([3,5,7,11]),)) This method is intented to be used on queries that return results, i.e. SELECT. After calling this method AND reading all rows from, result rows_affected property contains number of rows returned by last command (this is how MS SQL returns it). """ log("_mssql.MSSQLConnection.execute_query() BEGIN") self.format_and_run_query(query_string, params) self.get_result() log("_mssql.MSSQLConnection.execute_query() END") cpdef execute_row(self, query_string, params=None): """ execute_row(query_string, params=None) This method sends a query to the MS SQL Server to which this object instance is connected, then returns first row of data from result. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. This method accepts Python formatting. Please see execute_query() for details. This method is useful if you want just a single row and don't want or don't need to iterate, as in: conn.execute_row('SELECT * FROM employees WHERE id=%d', 13) This method works exactly the same as 'iter(conn).next()'. Remaining rows, if any, can still be iterated after calling this method. """ log("_mssql.MSSQLConnection.execute_row()") self.format_and_run_query(query_string, params) return self.fetch_next_row(0, ROW_FORMAT_DICT) cpdef execute_scalar(self, query_string, params=None): """ execute_scalar(query_string, params=None) This method sends a query to the MS SQL Server to which this object instance is connected, then returns first column of first row from result. An exception is raised on failure. If there are pending results or rows prior to executing this command, they are silently discarded. This method accepts Python formatting. Please see execute_query() for details. This method is useful if you want just a single value, as in: conn.execute_scalar('SELECT COUNT(*) FROM employees') This method works in the same way as 'iter(conn).next()[0]'. Remaining rows, if any, can still be iterated after calling this method. """ cdef RETCODE rtc log("_mssql.MSSQLConnection.execute_scalar()") self.format_and_run_query(query_string, params) self.get_result() with nogil: rtc = dbnextrow(self.dbproc) self._rows_affected = dbcount(self.dbproc) if rtc == NO_MORE_ROWS: self.clear_metadata() self.last_dbresults = 0 return None return self.get_row(rtc, ROW_FORMAT_TUPLE)[0] cdef fetch_next_row(self, int throw, int row_format): cdef RETCODE rtc log("_mssql.MSSQLConnection.fetch_next_row() BEGIN") try: self.get_result() if self.last_dbresults == NO_MORE_RESULTS: log("_mssql.MSSQLConnection.fetch_next_row(): NO MORE RESULTS") self.clear_metadata() if throw: raise StopIteration return None with nogil: rtc = dbnextrow(self.dbproc) check_cancel_and_raise(rtc, self) if rtc == NO_MORE_ROWS: log("_mssql.MSSQLConnection.fetch_next_row(): NO MORE ROWS") self.clear_metadata() # 'rows_affected' is nonzero only after all records are read self._rows_affected = dbcount(self.dbproc) if throw: raise StopIteration return None return self.get_row(rtc, row_format) finally: log("_mssql.MSSQLConnection.fetch_next_row() END") cdef format_and_run_query(self, query_string, params=None): """ This is a helper function, which does most of the work needed by any execute_*() function. It returns NULL on error, None on success. """ cdef RETCODE rtc # For Python 3, we need to convert unicode to byte strings cdef bytes query_string_bytes cdef char *query_string_cstr log("_mssql.MSSQLConnection.format_and_run_query() BEGIN") try: # Cancel any pending results self.cancel() if params: query_string = self.format_sql_command(query_string, params) # For Python 3, we need to convert unicode to byte strings query_string_bytes = ensure_bytes(query_string, self.charset) query_string_cstr = query_string_bytes log(query_string_cstr) if self.debug_queries: sys.stderr.write("#%s#\n" % query_string) # Prepare the query buffer dbcmd(self.dbproc, query_string_cstr) # Execute the query rtc = db_sqlexec(self.dbproc) check_cancel_and_raise(rtc, self) finally: log("_mssql.MSSQLConnection.format_and_run_query() END") cdef format_sql_command(self, format, params=None): log("_mssql.MSSQLConnection.format_sql_command()") return _substitute_params(format, params, self.charset) def get_header(self): """ get_header() -- get the Python DB-API compliant header information. This method is infrastructure and doesn't need to be called by your code. It returns a list of 7-element tuples describing the current result header. Only name and DB-API compliant type is filled, rest of the data is None, as permitted by the specs. """ cdef int col log("_mssql.MSSQLConnection.get_header() BEGIN") try: self.get_result() if self.num_columns == 0: log("_mssql.MSSQLConnection.get_header(): num_columns == 0") return None header_tuple = [] for col in xrange(1, self.num_columns + 1): col_name = self.column_names[col - 1] col_type = self.column_types[col - 1] header_tuple.append((col_name, col_type, None, None, None, None, None)) return tuple(header_tuple) finally: log("_mssql.MSSQLConnection.get_header() END") def get_iterator(self, int row_format): """ get_iterator(row_format) -- allows the format of the iterator to be specified While the iter(conn) call will always return a dictionary, this method allows the return type of the row to be specified. """ assert_connected(self) clr_err(self) return MSSQLRowIterator(self, row_format) cdef get_result(self): cdef int coltype cdef char log_message[200] log("_mssql.MSSQLConnection.get_result() BEGIN") try: if self.last_dbresults: log("_mssql.MSSQLConnection.get_result(): last_dbresults == True, return None") return None self.clear_metadata() # Since python doesn't have a do/while loop do it this way while True: with nogil: self.last_dbresults = dbresults(self.dbproc) self.num_columns = dbnumcols(self.dbproc) if self.last_dbresults != SUCCEED or self.num_columns > 0: break check_cancel_and_raise(self.last_dbresults, self) self._rows_affected = dbcount(self.dbproc) if self.last_dbresults == NO_MORE_RESULTS: self.num_columns = 0 log("_mssql.MSSQLConnection.get_result(): NO_MORE_RESULTS, return None") return None self.num_columns = dbnumcols(self.dbproc) snprintf(log_message, sizeof(log_message), "_mssql.MSSQLConnection.get_result(): num_columns = %d", self.num_columns) log_message[ sizeof(log_message) - 1 ] = b'\0' log(log_message) column_names = list() column_types = list() for col in xrange(1, self.num_columns + 1): col_name = dbcolname(self.dbproc, col) if not col_name: self.num_columns -= 1 return None column_name = col_name.decode(self._charset) column_names.append(column_name) coltype = dbcoltype(self.dbproc, col) column_types.append(get_api_coltype(coltype)) self.column_names = tuple(column_names) self.column_types = tuple(column_types) finally: log("_mssql.MSSQLConnection.get_result() END") cdef get_row(self, int row_info, int row_format): cdef DBPROCESS *dbproc = self.dbproc cdef int col cdef int col_type cdef int len cdef BYTE *data cdef tuple trecord cdef dict drecord log("_mssql.MSSQLConnection.get_row()") if PYMSSQL_DEBUG == 1: global _row_count _row_count += 1 if row_format == _ROW_FORMAT_TUPLE: trecord = PyTuple_New(self.num_columns) elif row_format == _ROW_FORMAT_DICT: drecord = dict() for col in xrange(1, self.num_columns + 1): with nogil: data = get_data(dbproc, row_info, col) col_type = get_type(dbproc, row_info, col) len = get_length(dbproc, row_info, col) if data == NULL: value = None else: IF PYMSSQL_DEBUG == 1: global _row_count fprintf(stderr, 'Processing row %d, column %d,' \ 'Got data=%x, coltype=%d, len=%d\n', _row_count, col, data, col_type, len) value = self.convert_db_value(data, col_type, len) if row_format == _ROW_FORMAT_TUPLE: Py_INCREF(value) PyTuple_SetItem(trecord, col - 1, value) elif row_format == _ROW_FORMAT_DICT: name = self.column_names[col - 1] drecord[col - 1] = value if name: drecord[name] = value if row_format == _ROW_FORMAT_TUPLE: return trecord elif row_format == _ROW_FORMAT_DICT: return drecord def init_procedure(self, procname): """ init_procedure(procname) -- creates and returns a MSSQLStoredProcedure object. This methods initializes a stored procedure or function on the server and creates a MSSQLStoredProcedure object that allows parameters to be bound. """ log("_mssql.MSSQLConnection.init_procedure()") return MSSQLStoredProcedure(procname.encode(self.charset), self) def nextresult(self): """ nextresult() -- move to the next result, skipping all pending rows. This method fetches and discards any rows remaining from the current resultset, then it advances to the next (if any) resultset. Returns True if the next resultset is available, otherwise None. """ cdef RETCODE rtc log("_mssql.MSSQLConnection.nextresult()") assert_connected(self) clr_err(self) rtc = dbnextrow(self.dbproc) check_cancel_and_raise(rtc, self) while rtc != NO_MORE_ROWS: rtc = dbnextrow(self.dbproc) check_cancel_and_raise(rtc, self) self.last_dbresults = 0 self.get_result() if self.last_dbresults != NO_MORE_RESULTS: return 1 def select_db(self, dbname): """ select_db(dbname) -- Select the current database. This function selects the given database. An exception is raised on failure. """ cdef RETCODE rtc log("_mssql.MSSQLConnection.select_db()") # For Python 3, we need to convert unicode to byte strings cdef bytes dbname_bytes = dbname.encode('utf-8') cdef char *dbname_cstr = dbname_bytes with nogil: dbuse(self.dbproc, dbname_cstr) cdef bcp_init(self, object table_name): cdef DBPROCESS *dbproc = self.dbproc cdef RETCODE rtc cdef bytes table_name_bytes cdef char *table_name_cstr log("_mssql.MSSQLBCPContext.bcp_init()") table_name_bytes = ensure_bytes(table_name, self.charset) table_name_cstr = table_name_bytes with nogil: rtc = bcp_init(dbproc, table_name_cstr, NULL, NULL, DB_IN) check_cancel_and_raise(rtc, self) cdef bcp_hint(self, BYTE * value, int valuelen): cdef DBPROCESS *dbproc = self.dbproc cdef RETCODE rtc log("_mssql.MSSQLBCPContext.bcp_hint()") with nogil: rtc = bcp_options(dbproc, BCPHINTS, value, valuelen) check_cancel_and_raise(rtc, self) cdef bcp_bind(self, object value, int is_none, int column_db_type, int position, BYTE **data): cdef DBPROCESS *dbproc = self.dbproc cdef RETCODE rtc cdef int length = -1 log("_mssql.MSSQLBCPContext.bcp_bind()") self.convert_python_value(value, data, &column_db_type, &length) if is_none: # It doesn't matter which vartype we choose here since we are passing NULL. rtc = bcp_bind( dbproc, # dbproc NULL, # varaddr 0, # prefixlen 0, # varlen NULL, # terminator 0, # termlen SQLVARCHAR, # vartype position # table_column ) else: rtc = bcp_bind( dbproc, # dbproc data[0], # varaddr 0, # prefixlen length, # varlen NULL, # terminator 0, # termlen column_db_type, # vartype position # table_column ) check_cancel_and_raise(rtc, self) cdef bcp_batch(self): cdef DBPROCESS *dbproc = self.dbproc cdef int rows_inserted = -1 log("_mssql.MSSQLBCPContext.bcp_batch()") with nogil: rows_inserted = bcp_batch(dbproc) if rows_inserted == -1: raise_MSSQLDatabaseException(self) cpdef bcp_sendrow(self, object element, object column_ids): cdef DBPROCESS *dbproc = self.dbproc cdef RETCODE rtc cdef int length = len(element) cdef int idx = -1 cdef BYTE **datas = NULL cdef int db_type = -1 cdef int is_none = -1 cdef int column_id = -1 log("_mssql.MSSQLBCPContext.bcp_sendrow()") try: datas = PyMem_Malloc(length * sizeof(BYTE *)) memset(datas, 0, length * sizeof(BYTE *)) try: for idx, col_value in enumerate(element): if column_ids is None: column_id = idx + 1 else: try: column_id = column_ids[idx] except IndexError: raise ValueError("Too few column IDs provided") if col_value is None: db_type = 0 is_none = 1 else: db_type = py2db_type(type(col_value), col_value) is_none = 0 self.bcp_bind(col_value, is_none, db_type, column_id, &datas[idx]) rtc = bcp_sendrow(dbproc) check_cancel_and_raise(rtc, self) finally: for idx in range(0, length): PyMem_Free(datas[idx]) finally: PyMem_Free(datas) cdef bcp_done(self): cdef DBPROCESS *dbproc = self.dbproc cdef int rows_inserted = -1 log("_mssql.MSSQLBCPContext.bcp_done()") with nogil: rows_inserted = bcp_done(dbproc) if rows_inserted == -1: raise_MSSQLDatabaseException(self) ################################## ## MSSQL Stored Procedure Class ## ################################## cdef class MSSQLStoredProcedure: property connection: """The underlying MSSQLConnection object.""" def __get__(self): return self.conn property name: """The name of the procedure that this object represents.""" def __get__(self): return self.procname property parameters: """The parameters that have been bound to this procedure.""" def __get__(self): return self.params def __init__(self, bytes name, MSSQLConnection connection): cdef RETCODE rtc log("_mssql.MSSQLStoredProcedure.__init__()") # We firstly want to check if tdsver is >= 7 as anything less # doesn't support remote procedure calls. cdef int version = dbtds(connection.dbproc) if connection.tds_version is None or connection.tds_version < 7: raise MSSQLDriverException("Stored Procedures aren't " "supported with a TDS version less than 7. Got %r (%r)" % (connection.tds_version, version)) self.conn = connection self.dbproc = connection.dbproc self.procname = name self.params = dict() self.output_indexes = list() self.param_count = 0 self.had_positional = False with nogil: rtc = dbrpcinit(self.dbproc, self.procname, 0) check_cancel_and_raise(rtc, self.conn) def __dealloc__(self): cdef _mssql_parameter_node *n cdef _mssql_parameter_node *p log("_mssql.MSSQLStoredProcedure.__dealloc__()") n = self.params_list p = NULL while n != NULL: PyMem_Free(n.value) p = n n = n.next PyMem_Free(p) def bind(self, object value, int dbtype, str param_name=None, int output=False, int null=False, int max_length=-1): """ bind(value, data_type, param_name = None, output = False, null = False, max_length = -1) -- bind a parameter This method binds a parameter to the stored procedure. """ cdef int length = -1 cdef RETCODE rtc cdef BYTE status cdef BYTE *data cdef bytes param_name_bytes cdef char *param_name_cstr cdef _mssql_parameter_node *pn log("_mssql.MSSQLStoredProcedure.bind()") # Set status according to output being True or False status = DBRPCRETURN if output else 0 # Convert the PyObject to the db type self.conn.convert_python_value(value, &data, &dbtype, &length) # We support nullable parameters by just not binding them if dbtype in (SQLINTN, SQLBITN) and data == NULL: return # Store the converted parameter in our parameter list so we can # free() it later. if data != NULL: pn = <_mssql_parameter_node *>PyMem_Malloc(sizeof(_mssql_parameter_node)) if pn == NULL: raise MSSQLDriverException('Out of memory') pn.next = self.params_list pn.value = data self.params_list = pn # We may need to set the data length depending on the type being # passed to the server here. if dbtype in (SQLVARCHAR, SQLCHAR, SQLTEXT, SQLBINARY, SQLVARBINARY, SQLIMAGE): if null or data == NULL: length = 0 if not output: max_length = -1 # only set the length for strings, binary may contain NULLs elif dbtype in (SQLVARCHAR, SQLCHAR, SQLTEXT): length = strlen(data) else: # Fixed length data type if null or (output and dbtype not in (SQLDECIMAL, SQLNUMERIC)): length = 0 max_length = -1 # Add some monkey fixing for nullable bit types if dbtype == SQLBITN: if output: max_length = 1 length = 0 else: length = 1 if status != DBRPCRETURN: max_length = -1 if param_name: param_name_bytes = param_name.encode('ascii') param_name_cstr = param_name_bytes if self.had_positional: raise MSSQLDriverException('Cannot bind named parameter after positional') else: param_name_cstr = '' self.had_positional = True IF PYMSSQL_DEBUG == 1: sys.stderr.write( "\n--- rpc_bind(name = '%s', status = %d, " "max_length = %d, data_type = %d, data_length = %d\n" % (param_name, status, max_length, dbtype, length) ) with nogil: rtc = dbrpcparam(self.dbproc, param_name_cstr, status, dbtype, max_length, length, data) check_cancel_and_raise(rtc, self.conn) # Store the value in the parameters dictionary for returning # later, by name if that has been supplied. if param_name: self.params[param_name] = value self.params[self.param_count] = value if output: self.output_indexes.append(self.param_count) self.param_count += 1 def execute(self): cdef RETCODE rtc cdef int output_count, i, type, length cdef char *param_name_bytes cdef BYTE *data log("_mssql.MSSQLStoredProcedure.execute()") # Cancel any pending results as this throws a server error # otherwise. db_cancel(self.conn) # Send the RPC request with nogil: rtc = dbrpcsend(self.dbproc) check_cancel_and_raise(rtc, self.conn) # Wait for results to come back and return the return code, optionally # calling wait_callback first... rtc = db_sqlok(self.dbproc) check_cancel_and_raise(rtc, self.conn) # Need to call this regardless of whether or not there are output # parameters in order for the return status to be correct. output_count = dbnumrets(self.dbproc) # If there are any output parameters then we are going to want to # set the values in the parameters dictionary. if output_count: for i in xrange(1, output_count + 1): with nogil: type = dbrettype(self.dbproc, i) param_name_bytes = dbretname(self.dbproc, i) length = dbretlen(self.dbproc, i) data = dbretdata(self.dbproc, i) value = self.conn.convert_db_value(data, type, length) if strlen(param_name_bytes): param_name = param_name_bytes.decode('utf-8') self.params[param_name] = value self.params[self.output_indexes[i-1]] = value # Get the return value from the procedure ready for return. return dbretstatus(self.dbproc) cdef int check_and_raise(RETCODE rtc, MSSQLConnection conn) except 1: if rtc == FAIL: return maybe_raise_MSSQLDatabaseException(conn) elif get_last_msg_str(conn): return maybe_raise_MSSQLDatabaseException(conn) cdef int check_cancel_and_raise(RETCODE rtc, MSSQLConnection conn) except 1: if rtc == FAIL: db_cancel(conn) return raise_MSSQLDatabaseException(conn) elif get_last_msg_str(conn): return maybe_raise_MSSQLDatabaseException(conn) cdef char *get_last_msg_str(MSSQLConnection conn): return conn.last_msg_str if conn != None else _mssql_last_msg_str cdef char *get_last_msg_srv(MSSQLConnection conn): return conn.last_msg_srv if conn != None else _mssql_last_msg_srv cdef char *get_last_msg_proc(MSSQLConnection conn): return conn.last_msg_proc if conn != None else _mssql_last_msg_proc cdef int get_last_msg_no(MSSQLConnection conn): return conn.last_msg_no if conn != None else _mssql_last_msg_no cdef int get_last_msg_severity(MSSQLConnection conn): return conn.last_msg_severity if conn != None else _mssql_last_msg_severity cdef int get_last_msg_state(MSSQLConnection conn): return conn.last_msg_state if conn != None else _mssql_last_msg_state cdef int get_last_msg_line(MSSQLConnection conn): return conn.last_msg_line if conn != None else _mssql_last_msg_line cdef int maybe_raise_MSSQLDatabaseException(MSSQLConnection conn) except 1: if get_last_msg_severity(conn) < min_error_severity: return 0 return raise_MSSQLDatabaseException(conn) cdef int raise_MSSQLDatabaseException(MSSQLConnection conn) except 1: error_msg = get_last_msg_str(conn) if len(error_msg) == 0: error_msg = b"Unknown error" ex = MSSQLDatabaseException((get_last_msg_no(conn), error_msg)) (ex).text = error_msg (ex).srvname = get_last_msg_srv(conn) (ex).procname = get_last_msg_proc(conn) (ex).number = get_last_msg_no(conn) (ex).severity = get_last_msg_severity(conn) (ex).state = get_last_msg_state(conn) (ex).line = get_last_msg_line(conn) db_cancel(conn) clr_err(conn) raise ex cdef void assert_connected(MSSQLConnection conn) except *: log("_mssql.assert_connected()") if not conn.connected: raise MSSQLDriverException("Not connected to any MS SQL server") cdef inline BYTE *get_data(DBPROCESS *dbproc, int row_info, int col) nogil: return dbdata(dbproc, col) if row_info == REG_ROW else \ dbadata(dbproc, row_info, col) cdef inline int get_type(DBPROCESS *dbproc, int row_info, int col) nogil: return dbcoltype(dbproc, col) if row_info == REG_ROW else \ dbalttype(dbproc, row_info, col) cdef inline int get_length(DBPROCESS *dbproc, int row_info, int col) nogil: return dbdatlen(dbproc, col) if row_info == REG_ROW else \ dbadlen(dbproc, row_info, col) ###################### ## Helper Functions ## ###################### cdef int get_api_coltype(int coltype): if coltype in (SQLBIT, SQLINT1, SQLINT2, SQLINT4, SQLINT8, SQLINTN, SQLFLT4, SQLFLT8, SQLFLTN): return NUMBER elif coltype in (SQLMONEY, SQLMONEY4, SQLMONEYN, SQLNUMERIC, SQLDECIMAL): return DECIMAL elif coltype in (SQLDATETIME, SQLDATETIM4, SQLDATETIMN): return DATETIME elif coltype in (SQLVARCHAR, SQLCHAR, SQLTEXT): return STRING else: return BINARY cdef char *_remove_locale(char *s, size_t buflen): cdef char c cdef char *stripped = s cdef int i, x = 0, last_sep = -1 for i, c in enumerate(s[0:buflen]): if c in (b',', b'.'): last_sep = i for i, c in enumerate(s[0:buflen]): if (c >= b'0' and c <= b'9') or c in (b'+', b'-'): stripped[x] = c x += 1 elif i == last_sep: stripped[x] = c x += 1 stripped[x] = 0 return stripped def remove_locale(bytes value): cdef char *s = value cdef size_t l = strlen(s) return _remove_locale(s, l) cdef int _tds_ver_str_to_constant(verstr) except -1: """ http://www.freetds.org/userguide/choosingtdsprotocol.htm """ if verstr == '4.2': return DBVERSION_42 if verstr == '7.0': return DBVERSION_70 if verstr in ('7.1', '8.0'): return DBVERSION_71 if verstr == '7.2': return DBVERSION_72 if verstr == '7.3': return DBVERSION_73 if verstr == '8.0': return DBVERSION_71 raise MSSQLException('unrecognized tds version: %s' % verstr) ####################### ## Quoting Functions ## ####################### cdef _quote_simple_value(value, charset='utf8'): if value == None: return b'NULL' if isinstance(value, bool): return '1' if value else '0' if isinstance(value, float): return repr(value).encode(charset) if isinstance(value, (int, long, decimal.Decimal)): return str(value).encode(charset) if isinstance(value, uuid.UUID): return _quote_simple_value(str(value)) if isinstance(value, unicode): return ("N'" + value.replace("'", "''") + "'").encode(charset) if isinstance(value, bytearray): return b'0x' + binascii.hexlify(bytes(value)) if isinstance(value, (str, bytes)): # see if it can be decoded as ascii if there are no null bytes if b'\0' not in value: try: value.decode('ascii') return b"'" + value.replace(b"'", b"''") + b"'" except UnicodeDecodeError: pass # Python 3: handle bytes # @todo - Marc - hack hack hack if isinstance(value, bytes): return b'0x' + binascii.hexlify(value) # will still be string type if there was a null byte in it or if the # decoding failed. In this case, just send it as hex. if isinstance(value, str): return '0x' + value.encode('hex') if isinstance(value, datetime.datetime): return "'%04d-%02d-%02d %02d:%02d:%02d.%03d'" % ( value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond / 1000) if isinstance(value, datetime.date): return "'%04d-%02d-%02d'" % ( value.year, value.month, value.day) return None cdef _quote_or_flatten(data, charset='utf8'): result = _quote_simple_value(data, charset) if result is not None: return result if not issubclass(type(data), (list, tuple)): raise ValueError('expected a simple type, a tuple or a list') quoted = [] for value in data: value = _quote_simple_value(value, charset) if value is None: raise ValueError('found an unsupported type') quoted.append(value) return b'(' + b','.join(quoted) + b')' # This function is supposed to take a simple value, tuple or dictionary, # normally passed in via the params argument in the execute_* methods. It # then quotes and flattens the arguments and returns then. cdef _quote_data(data, charset='utf8'): result = _quote_simple_value(data) if result is not None: return result if issubclass(type(data), dict): result = {} for k, v in data.iteritems(): result[k] = _quote_or_flatten(v, charset) return result if issubclass(type(data), tuple): result = [] for v in data: result.append(_quote_or_flatten(v, charset)) return tuple(result) raise ValueError('expected a simple type, a tuple or a dictionary.') _re_pos_param = re.compile(br'(%([sd]))') _re_name_param = re.compile(br'(%\(([^\)]+)\)(?:[sd]))') cdef _substitute_params(toformat, params, charset): if params is None: return toformat if not issubclass(type(params), (bool, int, long, float, unicode, str, bytes, bytearray, dict, tuple, datetime.datetime, datetime.date, dict, decimal.Decimal, uuid.UUID)): raise ValueError("'params' arg (%r) can be only a tuple or a dictionary." % type(params)) if charset: quoted = _quote_data(params, charset) else: quoted = _quote_data(params) # positional string substitution now requires a tuple if hasattr(quoted, 'startswith'): quoted = (quoted,) if isinstance(toformat, unicode): toformat = toformat.encode(charset) if isinstance(params, dict): """ assume name based substitutions """ offset = 0 for match in _re_name_param.finditer(toformat): param_key = match.group(2).decode(charset) if not param_key in params: raise ValueError('params dictionary did not contain value for placeholder: %s' % param_key) # calculate string positions so we can keep track of the offset to # be used in future substitutions on this string. This is # necessary b/c the match start() and end() are based on the # original string, but we modify the original string each time we # loop, so we need to make an adjustment for the difference between # the length of the placeholder and the length of the value being # substituted param_val = quoted[param_key] param_val_len = len(param_val) placeholder_len = len(match.group(1)) offset_adjust = param_val_len - placeholder_len # do the string substitution match_start = match.start(1) + offset match_end = match.end(1) + offset toformat = toformat[:match_start] + ensure_bytes(param_val, charset) + toformat[match_end:] # adjust the offset for the next usage offset += offset_adjust else: """ assume position based substitutions """ offset = 0 for count, match in enumerate(_re_pos_param.finditer(toformat)): # calculate string positions so we can keep track of the offset to # be used in future substitutions on this string. This is # necessary b/c the match start() and end() are based on the # original string, but we modify the original string each time we # loop, so we need to make an adjustment for the difference between # the length of the placeholder and the length of the value being # substituted try: param_val = quoted[count] except IndexError: raise ValueError('more placeholders in sql than params available') param_val_len = len(param_val) placeholder_len = 2 offset_adjust = param_val_len - placeholder_len # do the string substitution match_start = match.start(1) + offset match_end = match.end(1) + offset toformat = toformat[:match_start] + ensure_bytes(param_val, charset) + toformat[match_end:] #print(param_val, param_val_len, offset_adjust, match_start, match_end) # adjust the offset for the next usage offset += offset_adjust return toformat # We'll add these methods to the module to allow for unit testing of the # underlying C methods. def quote_simple_value(value): return _quote_simple_value(value) def quote_or_flatten(data): return _quote_or_flatten(data) def quote_data(data): return _quote_data(data) def substitute_params(toformat, params, charset='utf8'): return _substitute_params(toformat, params, charset) ########################### ## Compatibility Aliases ## ########################### def connect(*args, **kwargs): return MSSQLConnection(*args, **kwargs) MssqlDatabaseException = MSSQLDatabaseException MssqlDriverException = MSSQLDriverException MssqlConnection = MSSQLConnection ########################### ## Test Helper Functions ## ########################### def test_err_handler(connection, int severity, int dberr, int oserr, dberrstr, oserrstr): """ Expose err_handler function and its side effects to facilitate testing. """ cdef DBPROCESS *dbproc = NULL cdef char *dberrstrc = NULL cdef char *oserrstrc = NULL if dberrstr: dberrstr_byte_string = dberrstr.encode('UTF-8') dberrstrc = dberrstr_byte_string if oserrstr: oserrstr_byte_string = oserrstr.encode('UTF-8') oserrstrc = oserrstr_byte_string if connection: dbproc = (connection).dbproc results = ( err_handler(dbproc, severity, dberr, oserr, dberrstrc, oserrstrc), get_last_msg_str(connection), get_last_msg_no(connection), get_last_msg_severity(connection), get_last_msg_state(connection) ) clr_err(connection) return results ##################### ## Max Connections ## ##################### def get_max_connections(): """ Get maximum simultaneous connections db-lib will open to the server. """ return dbgetmaxprocs() def set_max_connections(int limit): """ Set maximum simultaneous connections db-lib will open to the server. :param limit: the connection limit :type limit: int """ dbsetmaxprocs(limit) def get_dbversion(): """ Return string representing the version of db-lib. """ return dbversion().decode('ascii') cdef void init_mssql(): if dbinit() == FAIL: raise MSSQLDriverException("dbinit() failed") dberrhandle(err_handler) dbmsghandle(msg_handler) init_mssql() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/src/pymssql/_pymssql.pyx0000644000000000000000000005540100000000000020161 0ustar00rootroot00000000000000"""DB-SIG compliant module for communicating with MS SQL servers""" # pymssql.pyx # # Copyright (C) 2003 Joon-cheol Park # 2008 Andrzej Kukula # 2009 Damien Churchill # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA import datetime import time from pymssql import _mssql from pymssql cimport _mssql from cpython cimport bool from libc.string cimport strlen from .sqlfront cimport BYTE cdef extern from "version.h": const char *PYMSSQL_VERSION __author__ = 'Damien Churchill ' __full_version__ = PYMSSQL_VERSION.decode('ascii') __version__ = '.'.join(__full_version__.split('.')[:3]) VERSION = tuple(int(c) if c.isdigit() else c for c in __full_version__.split('.')[:3]) # Strives for compliance with DB-API 2.0 (PEP 249) # http://www.python.org/dev/peps/pep-0249/ apilevel = '2.0' # module may be shared, but not connections threadsafety = 1 # this module uses extended python format codes paramstyle = 'pyformat' from pymssql._mssql import set_wait_callback # store a tuple of programming error codes cdef object prog_errors = ( 102, # syntax error 207, # invalid column name 208, # invalid object name 2812, # unknown procedure 4104 # multi-part identifier could not be bound ) # store a tuple of integrity error codes cdef object integrity_errors = ( 515, # NULL insert 547, # FK related 2601, # violate unique index 2627, # violate UNIQUE KEY constraint ) class DBAPIType: def __init__(self, value): self.value = value def __cmp__(self, other): if other == self.value: return 0 if other < self.value: return 1 else: return -1 def __eq__(self, other): return (other == self.value) def __repr__(self): return '' % self.value STRING = DBAPIType(_mssql.STRING) BINARY = DBAPIType(_mssql.BINARY) NUMBER = DBAPIType(_mssql.NUMBER) DATETIME = DBAPIType(_mssql.DATETIME) DECIMAL = DBAPIType(_mssql.DECIMAL) Date = datetime.date Time = datetime.time Timestamp = datetime.datetime DateFromTicks = lambda ticks: Date(*time.localtime(ticks)[:3]) TimeFromTicks = lambda ticks: Time(*time.localtime(ticks)[3:6]) TimestampFromTicks = lambda ticks: Timestamp(*time.localtime(ticks)[:6]) Binary = bytearray # Bulk copy hints cdef char* TABLOCK = "TABLOCK" cdef char* CHECK_CONSTRAINTS = "CHECK_CONSTRAINTS" cdef char* FIRE_TRIGGERS = "FIRE_TRIGGERS" cdef int TABLOCK_LEN = strlen(TABLOCK) cdef int CHECK_CONSTRAINTS_LEN = strlen(CHECK_CONSTRAINTS) cdef int FIRE_TRIGGERS_LEN = strlen(FIRE_TRIGGERS) try: StandardError except NameError: StandardError = Exception # exception hierarchy class Warning(StandardError): pass class Error(StandardError): pass class InterfaceError(Error): pass class DatabaseError(Error): pass class DataError(Error): pass class OperationalError(DatabaseError): pass class IntegrityError(DatabaseError): pass class InternalError(DatabaseError): pass class ProgrammingError(DatabaseError): pass class NotSupportedError(DatabaseError): pass class ColumnsWithoutNamesError(InterfaceError): def __init__(self, columns_without_names): self.columns_without_names = columns_without_names def __str__(self): return 'Specified as_dict=True and ' \ 'there are columns with no names: %r' \ % (self.columns_without_names,) def row2dict(row): """Filter dict so it only has string keys; used when as_dict == True""" return dict([(k, v) for k, v in row.items() if hasattr(k, 'startswith')]) # stored procedure output parameter cdef class output: cdef object _type cdef object _value property type: """ This is the type of the parameter. """ def __get__(self): return self._type property value: """ This is the value of the parameter. """ def __get__(self): return self._value def __init__(self, param_type, value=None): self._type = param_type self._value = value ###################### ## Connection class ## ###################### cdef class Connection: """ This class represents an MS-SQL database connection. """ cdef bool _as_dict cdef bool _autocommit cdef _mssql.MSSQLConnection conn property as_dict: """ Instructs all cursors this connection creates to return results as a dictionary rather than a tuple. """ def __get__(self): return self._as_dict def __set__(self, value): self._as_dict = value property autocommit_state: """ The current state of autocommit on the connection. """ def __get__(self): return self._autocommit property _conn: """ INTERNAL PROPERTY. Returns the _mssql.MSSQLConnection object, and raise exception if it's set to None. It's easier than adding the necessary checks to every other method. """ def __get__(self): if self.conn == None: raise InterfaceError('Connection is closed.') return self.conn def __init__(self, conn, as_dict, autocommit): self.conn = conn self._autocommit = autocommit self.as_dict = as_dict if not autocommit: try: self._conn.execute_non_query('BEGIN TRAN') except Exception, e: raise OperationalError('Cannot start transaction: ' + str(e.args[0])) def __dealloc__(self): if self.conn: self.close() def autocommit(self, status): """ Turn autocommit ON or OFF. """ if status == self._autocommit: return tran_type = 'ROLLBACK' if status else 'BEGIN' self._conn.execute_non_query('%s TRAN' % tran_type) self._autocommit = status def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def close(self): """ Close the connection to the database. Implicitly rolls back all uncommitted transactions. """ if self.conn: self.conn.close() self.conn = None def commit(self): """ Commit transaction which is currently in progress. """ if self._autocommit == True: return try: self._conn.execute_non_query('COMMIT TRAN') self._conn.execute_non_query('BEGIN TRAN') except Exception, e: raise OperationalError('Cannot commit transaction: ' + str(e.args[0])) def cursor(self, as_dict=None): """ Return cursor object that can be used to make queries and fetch results from the database. """ if as_dict is None: as_dict = self.as_dict return Cursor(self, as_dict) def rollback(self): """ Roll back transaction which is currently in progress. """ if self._autocommit == True: return try: self._conn.execute_non_query('ROLLBACK TRAN') except _mssql.MSSQLException, e: # PEP 249 indicates that we have contract with the user that we will # always have a transaction in place if autocommit is False. # Therefore, it seems logical to ignore this exception since it # indicates a situation we shouldn't ever encounter anyway. However, # it can happen when an error is severe enough to cause a # "batch-abort". In that case, SQL Server *implicitly* rolls back # the transaction for us (how helpful!). But there doesn't seem # to be any way for us to know if an error is severe enough to cause # a batch abort: # http://stackoverflow.com/questions/5877162/why-does-microsoft-sql-server-implicitly-rollback-when-a-create-statement-fails # # the alternative is to do 'select @@trancount' before each rollback # but that is slower and doesn't seem to offer any benefit. if 'The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION' not in str(e): raise try: self._conn.execute_non_query('BEGIN TRAN') except Exception, e: raise OperationalError('Cannot begin transaction: ' + str(e.args[0])) cpdef bulk_copy(self, table_name, elements, column_ids=None, batch_size=1000, tablock=False, check_constraints=False, fire_triggers=False): cdef int batch_counter = 0 cdef _mssql.MSSQLConnection conn = self._conn conn.bcp_init(table_name) if tablock: conn.bcp_hint( TABLOCK, TABLOCK_LEN) if check_constraints: conn.bcp_hint( CHECK_CONSTRAINTS, CHECK_CONSTRAINTS_LEN) if fire_triggers: conn.bcp_hint( FIRE_TRIGGERS, FIRE_TRIGGERS_LEN) for element in elements: if batch_counter == batch_size: conn.bcp_batch() batch_counter = 0 self._conn.bcp_sendrow(element, column_ids) batch_counter += 1 conn.bcp_done() ################## ## Cursor class ## ################## cdef class Cursor: """ This class represents a database cursor, which is used to issue queries and fetch results from a database connection. """ cdef Connection conn cdef public tuple description cdef int batchsize cdef int _batchsize cdef int _rownumber cdef bool as_dict cdef object _returnvalue property connection: def __get__(self): return self.conn property lastrowid: def __get__(self): return self.conn.conn.identity property rowcount: def __get__(self): return self._rownumber property returnvalue: def __get__(self): return self._returnvalue property rownumber: def __get__(self): return self._rownumber property _source: def __get__(self): if self.conn == None: raise InterfaceError('Cursor is closed.') return self.conn def __init__(self, conn, as_dict): self.conn = conn self.description = None self._batchsize = 1 self._rownumber = 0 self._returnvalue = None self.as_dict = as_dict def __iter__(self): """ Return self to make cursors compatibile with Python iteration protocol. """ return self def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def callproc(self, str procname, parameters=()): """ Call a stored procedure with the given name. :param procname: The name of the procedure to call :type procname: str :keyword parameters: The optional parameters for the procedure :type parameters: sequence """ self._returnvalue = None proc = self._source._conn.init_procedure(procname) for parameter in parameters: if type(parameter) is output: param_type = parameter.type param_value = parameter.value param_output = True else: param_type = type(parameter) param_value = parameter param_output = False db_type = _mssql.py2db_type(param_type, param_value) proc.bind(param_value, db_type, output=param_output) try: self._returnvalue = proc.execute() self.description = self._source._conn.get_header() except _mssql.MSSQLDatabaseException, e: raise DatabaseError, e.args[0] return tuple([proc.parameters[p] for p in proc.parameters]) def close(self): """ Closes the cursor. The cursor is unusable from this point. """ self.conn = None self.description = None def execute(self, operation, params=()): self.description = None self._rownumber = 0 try: if not params: self._source._conn.execute_query(operation) else: self._source._conn.execute_query(operation, params) self.description = self._source._conn.get_header() self._rownumber = self._source._conn.rows_affected if self.as_dict and self.description: columns_without_names = [ idx for idx, column_descriptor in enumerate(self.description) if len(column_descriptor[0]) == 0 ] if columns_without_names: raise ColumnsWithoutNamesError(columns_without_names) except _mssql.MSSQLDatabaseException, e: if e.number in prog_errors: raise ProgrammingError, e.args[0] if e.number in integrity_errors: raise IntegrityError, e.args[0] raise OperationalError, e.args[0] except _mssql.MSSQLDriverException, e: raise InterfaceError, e.args[0] def executemany(self, operation, params_seq): self.description = None rownumber = 0 for params in params_seq: self.execute(operation, params) # support correct rowcount across multiple executes rownumber += self._rownumber self._rownumber = rownumber def nextset(self): try: if not self._source._conn.nextresult(): return None self._rownumber = 0 self.description = self._source._conn.get_header() return 1 except _mssql.MSSQLDatabaseException, e: raise OperationalError, e.args[0] except _mssql.MSSQLDriverException, e: raise InterfaceError, e.args[0] cdef getrow(self): """ Helper method used by fetchone and fetchmany to fetch and handle converting the row if as_dict = True. """ row_format = _mssql.ROW_FORMAT_DICT if self.as_dict else _mssql.ROW_FORMAT_TUPLE row = next(self._source._conn.get_iterator(row_format)) if not self.as_dict: return row return row2dict(row) def fetchone(self): if self.description is None: raise OperationalError('Statement not executed or executed statement has no resultset') try: return self.getrow() except StopIteration: self._rownumber = self._source._conn.rows_affected return None except _mssql.MSSQLDatabaseException, e: raise OperationalError, e.args[0] except _mssql.MSSQLDriverException, e: raise InterfaceError, e.args[0] def fetchmany(self, size=None): if self.description is None: raise OperationalError('Statement not executed or executed statement has no resultset') if size == None: size = self._batchsize self.batchsize = size try: rows = [] for i in xrange(size): try: rows.append(self.getrow()) except StopIteration: self._rownumber = self._source._conn.rows_affected break return rows except _mssql.MSSQLDatabaseException, e: raise OperationalError, e.args[0] except _mssql.MSSQLDriverException, e: raise InterfaceError, e.args[0] def fetchall(self): if self.description is None: raise OperationalError('Statement not executed or executed statement has no resultset') try: rows = [] while True: try: rows.append(self.getrow()) except StopIteration: break self._rownumber = self._source._conn.rows_affected return rows except _mssql.MSSQLDatabaseException, e: raise OperationalError, e.args[0] except _mssql.MSSQLDriverException, e: raise InterfaceError, e.args[0] def __next__(self): try: row = self.getrow() self._rownumber += 1 return row except _mssql.MSSQLDatabaseException, e: raise OperationalError, e.args[0] except _mssql.MSSQLDriverException, e: raise InterfaceError, e.args[0] def setinputsizes(self, sizes=None): """ This method does nothing, as permitted by DB-API specification. """ pass def setoutputsize(self, size=None, column=0): """ This method does nothing, as permitted by DB-API specification. """ pass def connect(server='.', user=None, password=None, database='', timeout=0, login_timeout=60, charset='UTF-8', as_dict=False, host='', appname=None, port='1433', encryption=None, conn_properties=None, autocommit=False, tds_version=None): """ Constructor for creating a connection to the database. Returns a Connection object. :param server: database host :type server: string :param user: database user to connect as. Default value: None. :type user: string :param password: user's password. Default value: None. :type password: string :param database: the database to initially connect to :type database: string :param timeout: query timeout in seconds, default 0 (no timeout) :type timeout: int :param login_timeout: timeout for connection and login in seconds, default 60 :type login_timeout: int :param charset: character set with which to connect to the database :type charset: string :keyword as_dict: whether rows should be returned as dictionaries instead of tuples. :type as_dict: boolean :keyword appname: Set the application name to use for the connection :type appname: string :keyword port: the TCP port to use to connect to the server :type port: string :keyword conn_properties: SQL queries to send to the server upon connection establishment. Can be a string or another kind of iterable of strings :keyword autocommit: Whether to use default autocommiting mode or not :type autocommit: boolean :keyword tds_version: TDS protocol version to use. :type tds_version: string """ # set the login timeout try: login_timeout = int(login_timeout) except ValueError: login_timeout = 0 _mssql.login_timeout = login_timeout # default query timeout try: timeout = int(timeout) except ValueError: timeout = 0 if host: server = host try: conn = _mssql.connect(server=server, encryption=encryption, user=user, password=password, charset=charset, database=database, appname=appname, port=port, tds_version=tds_version, conn_properties=conn_properties) except _mssql.MSSQLDatabaseException, e: raise OperationalError(e.args[0]) except _mssql.MSSQLDriverException, e: raise InterfaceError(e.args[0]) if timeout != 0: conn.query_timeout = timeout return Connection(conn, as_dict, autocommit) def get_max_connections(): """ Get the maximum number of simulatenous connections pymssql will open to the server. """ return _mssql.get_max_connections() def set_max_connections(int limit): """ Set maximum simultaneous connections db-lib will open to the server. :param limit: the connection limit :type limit: int """ _mssql.set_max_connections(limit) # Only recent versions of FreeTDS have the ct_config function # so this can break builds # Maybe later we can enable this or make it conditional DEF HAS_CT_CONFIG = False IF HAS_CT_CONFIG: cdef extern from "ctpublic.h": ctypedef int CS_INT ctypedef void CS_VOID struct _CS_CONTEXT ctypedef _CS_CONTEXT CS_CONTEXT ctypedef CS_INT CS_RETCODE int CS_GET, CS_VERSION CS_RETCODE ct_config(CS_CONTEXT * ctx, CS_INT action, CS_INT property, CS_VOID * buffer, CS_INT buflen, CS_INT * outlen) def get_freetds_version(): cdef CS_CONTEXT *ctx = NULL cdef char buf[256] cdef int outlen ret = ct_config(ctx, CS_GET, CS_VERSION, buf, 256, &outlen) return buf def get_dbversion(): """ Return string representing the version of db-lib. """ return _mssql.get_dbversion() def version_info(): """ Returns string with version information about pymssql, FreeTDS, Python and OS. """ import platform import sys import io output = io.StringIO() print("System:", file=output) print("platform.system() :", platform.system(), file=output) print("platform.architecture() :", platform.architecture(), file=output) if platform.system() != 'Windows': print("platform.libc_ver() :", platform.libc_ver(), file=output) print("Python:", file=output) print("sys.version :", sys.version, file=output) print("pymssql:", file=output) print("VERSION :", VERSION, file=output) print("version :", __version__, file=output) print("full_version :", __full_version__, file=output) print("dbversion :", get_dbversion(), file=output) print("_mssql:", file=output) print("VERSION :", _mssql.VERSION, file=output) print("version :", _mssql.__version__, file=output) print("full_version :", _mssql.__full_version__, file=output) print("dbversion :", _mssql.get_dbversion(), file=output) print("login_timeout :", _mssql.login_timeout, file=output) print("min_error_severity :", _mssql.min_error_severity, file=output) contents = output.getvalue() output.close() return contents ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/src/pymssql/sqlfront.pxd0000644000000000000000000006101100000000000020127 0ustar00rootroot00000000000000# Since Cython needs to know what you are using from header files this # definition is provided so it knows exactly what we are using from # FreeTDS. cdef extern from "sqlfront.h": ## Type Definitions ## cdef struct tds_dblib_dbprocess: pass cdef struct tds_sysdep_real32_type: pass cdef struct tds_sysdep_real64_type: pass ctypedef tds_dblib_dbprocess DBPROCESS cdef struct tds_dblib_loginrec: pass ctypedef tds_dblib_loginrec LOGINREC ctypedef void DBCURSOR ctypedef int BOOL ctypedef short int SHORT ctypedef unsigned char BYTE ctypedef int RETCODE ctypedef short unsigned int DBUSMALLINT ctypedef unsigned char DBBINARY ctypedef int DBBIT ctypedef unsigned char DBBOOL ctypedef char DBCHAR ctypedef int DBINT ctypedef tds_sysdep_real32_type DBREAL ctypedef tds_sysdep_real64_type DBFLT8 cdef struct DBMONEY: DBINT mnyhigh unsigned int mnylow cdef struct DBMONEY4: DBINT mny4 ctypedef unsigned char DBTINYINT ctypedef short int DBSMALLINT ctypedef struct DBDATETIME: DBINT dtdays DBINT dttime ctypedef struct DBDATETIME4: DBUSMALLINT days DBUSMALLINT minutes ctypedef struct DBCOL: DBINT SizeOfStruct DBCHAR * Name DBCHAR * ActualName DBCHAR * TableName SHORT Type DBINT UserType DBINT MaxLength BYTE Precision BYTE Scale BOOL VarLength BYTE Null BYTE CaseSensitive BYTE Updatable BOOL Identity ctypedef struct DBDATEREC: DBINT year DBINT month DBINT day DBINT dayofyear DBINT weekday DBINT hour DBINT minute DBINT second DBINT millisecond DBINT tzone ctypedef struct DBNUMERIC: BYTE precision BYTE scale BYTE array[33] ctypedef DBNUMERIC DBDECIMAL # Error handler callback ctypedef int(*EHANDLEFUNC)(DBPROCESS *, int, int, int, char *, char *) # Message handler callback ctypedef int(*MHANDLEFUNC)(DBPROCESS *, DBINT, int, int, char *, char *, char *, int) ## Constants ## int FAIL int SUCCEED int INT_CANCEL int NO_MORE_ROWS int NO_MORE_RESULTS int REG_ROW int DBNOERR int DBRPCRETURN int CI_ALTERNATE int CI_CURSOR int CI_REGULAR int MAXCOLNAMELEN ## Bulk Copy Constants ## int DB_IN int DB_OUT int BCPHINTS ## Version Constants ## int DBVERSION_42 int DBVERSION_70 int DBVERSION_71 int DBVERSION_72 ## Type Constants ## cdef enum: SYBBINARY SYBBIT SYBCHAR SYBDATETIME SYBDATETIME4 SYBDATETIMN SYBDECIMAL SYBFLT8 SYBFLTN SYBIMAGE SYBINT1 SYBINT2 SYBINT4 SYBINT8 SYBINTN SYBMONEY SYBMONEY4 SYBMONEYN SYBNUMERIC SYBREAL SYBTEXT SYBVARBINARY SYBVARCHAR ## Primary functions ## # See which version of db-lib is in use. # # Returns: # null-terminated ASCII string representing the version of db-lib. const char *dbversion() # Get address of compute column data. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # computeid of COMPUTE clause to which we're referring. # column Nth column in computeid, starting from 1. # # Returns: # pointer to columns' data buffer. # # Return values: # NULL no such compute id or column. BYTE * dbadata(DBPROCESS *, int, int) nogil # Get address of compute column data. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # computeid of COMPUTE clause to which we're referring. # column Nth column in computeid, starting from 1. # # Returns: # size of the data, in bytes # # Return values: # -1 no such column or computeid. # 0 data is NULL. DBINT dbadlen(DBPROCESS *, int, int) nogil # Get datatype for a compute column data. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # computeid of COMPUTE clause to which we're referring. # column Nth column in computeid, starting from 1. # # Returns: # SYB* datatype token # # Return values: # -1 no such column or computeid. int dbalttype(DBPROCESS *, int, int) nogil # Cancel the current command batch # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Returns: # SUCCEED always RETCODE dbcancel(DBPROCESS *) nogil # Close a connection to the server and free associated resources. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. void dbclose(DBPROCESS *) nogil # Append SQL to the command buffer. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # cmdstring SQL to append to the command buffer. # # Returns: # SUCCEED success # FAIL insufficient memory RETCODE dbcmd(DBPROCESS *, char *) # Return name of a regular result column. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # column Nth in the result set, starting with 1. # # Returns: # pointer to ASCII null-terminated string, the name of the column. # # Return values: # NULL column is not in range. char * dbcolname(DBPROCESS *, int) # Get the datatype of a regular result set column. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # column Nth column in computeid, starting from 1. # # Returns: # SYB* datatype token value, or zero if column is out of range. int dbcoltype(DBPROCESS *, int) nogil # Convert one datatype to another. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # srctype datatype of the data to convert # src buffer to convert # srclen length of src # desttype target datatype # dest output buffer # destlen size of dest # # Returns: # On success, the count of output bytes in dest, else -1. On # failure, it will call any user-supplied error handler. DBINT dbconvert(DBPROCESS *, int, BYTE *, DBINT, int, BYTE *, DBINT) ctypedef struct DBTYPEINFO: DBINT precision DBINT scale DBINT dbconvert_ps(DBPROCESS * dbprocess, int srctype, BYTE * src, DBINT srclen, int desttype, BYTE * dest, DBINT destlen, DBTYPEINFO * typeinfo) # Get count of rows processed. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # Returns: # * for insert/update/delete, count of rows affected. # * for select, count of rows returned, after all rows have been # fetched. DBINT dbcount(DBPROCESS *) nogil # Check if dbproc is an ex-parrot. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # Returns: # * for insert/update/delete, count of rows affected. # * for select, count of rows returned, after all rows have been DBBOOL dbdead(DBPROCESS *) # Get address of data in a regular result column. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # column Nth column in computeid, starting from 1. # # Returns: # pointer to the data, or NULL if data is NULL, or if column is # out of range BYTE * dbdata(DBPROCESS *, int) nogil # Break a DBDATETIME value into useful pieces. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # di output: structure to contain the exploded parts of # datetime. # datetime input: DBDATETIME to be converted. # # Return values: # SUCCEED always RETCODE dbdatecrack(DBPROCESS *, DBDATEREC *, DBDATETIME *) # Get size of current row's data in a regular result column. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # column Nth column in computeid, starting from 1. # # Returns: # size of the data, in bytes DBINT dbdatlen(DBPROCESS *, int) nogil # Set an error handler, for messages from db-lib. # # Parameters: # handler pointer to callback function that will handle errors. # Pass NULL to restore the default handler. # # Returns: # address of prior handler, or NULL if none was previously # installed. EHANDLEFUNC dberrhandle(EHANDLEFUNC) # Close server connections and free all related structures. void dbexit() # Get maximum simultaneous connections db-lib will open to the server. # # Returns: # size of the data, in bytes int dbgetmaxprocs() # Initialize db-lib # # Return values: # SUCCEED normal # FAIL cannot allocate an array of TDS_MAX_CONN TDSSOCKET # pointers. RETCODE dbinit() # Allocate a LOGINREC structure. # # Return values: # NULL the LOGINREC cannot be allocated. # LOGINREC* to valid memory LOGINREC * dblogin() # free the LOGINREC void dbloginfree(LOGINREC *) # Set a message handler, for messages from the server. # # Parameters: # handler address of the function that will process the messages. MHANDLEFUNC dbmsghandle(MHANDLEFUNC) # Get name of current database. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Returns: # current database name, as null-terminated ASCII string. char * dbname(DBPROCESS *) # Read result row into the row buffer and into any bound host variables. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Return values: # REG_ROW regular row has been read. # BUF_FULL reading the next row would cause the buffer to be # exceeded. No row was read from the server. # # Returns: # computeid when a compute row is read. RETCODE dbnextrow(DBPROCESS *) nogil # Return number of regular columns in a result set. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Returns: # number of columns in the result set row. int dbnumcols(DBPROCESS *) # Form a connection with the server. # # Parameters: # login LOGINREC* carrying the account information # server name of the dataserver to connect to # # Returns: # value pointer on successful login. # # Return values: # NULL insufficient memory, unable to connect for any reason DBPROCESS * dbopen(LOGINREC *, char *) nogil # Set up query results. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Return values: # SUCCEED some result are available. # FAIL query was not processed successfully by the server. # NO_MORE_RESULTS query produced no results. RETCODE dbresults(DBPROCESS *) nogil # Set maximum seconds db-lib waits for a server response to a login # attempt. # # Parameters: # seconds New limit for application. # # Returns: # SUCCEED always RETCODE dbsetlogintime(int) # Set maximum simultaneous connections db-lib will open to the server. # # Parameters: # maxprocs Limit for process. # # Returns: # SUCCEED always RETCODE dbsetmaxprocs(int) # Set maximum seconds db-lib waits for a server response to query. # # Parameters: # seconds New limit for application. # # Returns: # SUCCEED always RETCODE dbsettime(int) # Send the SQL command to the server and wait for an answer. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Return values: # SUCCEED query was processed without errors. # FAIL was returned by dbsqlsend() or dbsqlok() RETCODE dbsqlexec(DBPROCESS *) nogil # Send the SQL command to the server # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Return values: # SUCCEED query was processed without errors. # FAIL was returned by dbsqlsend() or dbsqlok() RETCODE dbsqlsend(DBPROCESS *) nogil # Get file descriptor of the socket used by a DBPROCESS to read data coming # from the server. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Return values: # An integer file descriptor used by the specified DBPROCESS to read # data coming from the server. int dbiordesc(DBPROCESS *) nogil # Wait for results of a query from the server. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Return values: # SUCCEED everything worked, fetch results with dbnextresults(). # FAIL SQL syntax error, typically. RETCODE dbsqlok(DBPROCESS *) nogil # Get the TDS version in use for dbproc. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # # Returns: # a DBTDS* token. # # Remarks: # The integer values of the constants are counterintuitive. int dbtds(DBPROCESS *) # Change current database # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # name database to use. # # Return values: # SUCCEED query was processed without errors. # FAIL query was not processed RETCODE dbuse(DBPROCESS *, char *) nogil ## End Primary functions ## ## Remote Procedure functions ## # Determine if query generated a return status number # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # Return values: # TRUE fetch return status with dbretstatus(). # FALSE no return status DBBOOL dbhasretstat(DBPROCESS *) # Get count of output parameters filled by a stored procedure # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # Returns: # How many, possibly zero. int dbnumrets(DBPROCESS *) # Get value of an output parameter filled by a stored procedure. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # retnum Nth parameter between 1 and the return value from # dbnumrets() # Returns: # Address of a return parameter value, or NULL if no such retnum. BYTE * dbretdata(DBPROCESS *, int) nogil # Get size of an output parameter filled by a stored procedure. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # retnum Nth parameter between 1 and the return value from # dbnumrets() # Returns: # Size of a return parameter value, or NULL if no such retnum. int dbretlen(DBPROCESS *, int) nogil # Get name of an output parameter filled by a stored procedure. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # retnum Nth parameter between 1 and the return value from # dbnumrets() # Returns: # ASCII null-terminated string, NULL if no such retnum. char * dbretname(DBPROCESS *, int) nogil # Fetch status value returned by query or remote procedure call. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # Returns: # The return value of the rpc call DBINT dbretstatus(DBPROCESS *) nogil # Get datatype of a stored procedure's return parameter. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # retnum Nth parameter between 1 and the return value from # dbnumrets() # Returns: # SYB* datatype token, or -1 if retnum is out of range. int dbrettype(DBPROCESS *, int) nogil # Initialize a remote procedure call. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # rpcname name of the stored procedure to be run. # options Only supported option would be DBRPCRECOMPILE, which causes # the stored procedure to be recompiled before executing. # Return values: # SUCCEED normal # FAIL on error RETCODE dbrpcinit(DBPROCESS *, char *, DBSMALLINT) nogil # Add a parameter to a remote procedure call. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # paramname literal name of the parameter, according to the stored # procedure (starts with '@'). Optional. If not used, # parameters will be passed in order instead of by name. # status must be DBRPCRETURN, if this parameter is a return # parameter, else 0. # type datatype of the value parameter e.g. SYBINT4, SYBCHAR # maxlen Maximum output size of the parameter's value to be returned # by the stored procedure, usually the size of your host # variable. Fixed-length datatypes take -1 (NULL or not). # Non-OUTPUT parameters also use -1. Use 0 to send a NULL # value for a variable length datatype. # datalen For variable-length datatypes, the byte size of the data # to be sent, exclusive of any null terminator. For # fixed-length datatypes use -1. To send a NULL value, use 0. # value Address of your host variable. # # Return values: # SUCCEED normal # FAIL on error RETCODE dbrpcparam(DBPROCESS *, char *, BYTE, int, DBINT, DBINT, BYTE *) nogil # Execute the procedure and free associated memory. # # Parameters: # dbproc contains all information needed by db-lib to manage # communications with the server. # Return values: # SUCCEED normal # FAIL on error RETCODE dbrpcsend(DBPROCESS *) nogil ## End Remote Procedure functions ## ## Bulk Copy Functions ## # Commit a set of rows to the table. # # Parameters # dbproc contains all information needed by db-lib to manage communications with the server. # # Returns # Count of rows saved, or -1 on error. DBINT bcp_batch (DBPROCESS *) nogil # Bind a program host variable to a database column. # # Parameters # # dbproc contains all information needed by db-lib to manage communications with the server. # varaddr address of host variable # prefixlen length of any prefix found at the beginning of varaddr, in bytes. # Use zero for fixed-length datatypes. # varlen bytes of data in varaddr. Zero for NULL, -1 for fixed-length datatypes. # terminator byte sequence that marks the end of the data in varaddr # termlen length of terminator # vartype datatype of the host variable # table_column Nth column, starting at 1, in the table. # # Return values: # SUCCEED normal # FAIL on error RETCODE bcp_bind (DBPROCESS *, BYTE *, int, DBINT, BYTE *, int, int, int) nogil # Conclude the transfer of data from program variables. # # Parameters # dbproc contains all information needed by db-lib to manage communications with the server. # # Returns # The count of rows saved, or -1 on error. DBINT bcp_done (DBPROCESS *) nogil # Set "hints" for uploading a file. A FreeTDS-only function. # # Parameters # dbproc contains all information needed by db-lib to manage communications with the server. # option symbolic constant indicating the option to be set, one of: # BCPLABELED Not implemented. # BCPHINTS The hint to be passed when the bulk-copy begins. # value The string constant for option a/k/a the hint. One of: # ORDER The data are ordered in accordance with the table's clustered index. # ROWS_PER_BATCH The batch size # KILOBYTES_PER_BATCH The approximate number of kilobytes to use for a batch size # TABLOCK Lock the table # CHECK_CONSTRAINTS Apply constraints # FIRE_TRIGGERS Fire any INSERT triggers on the target table # valuelen The strlen of value. RETCODE bcp_options (DBPROCESS *, int, BYTE *, int) nogil # Write data in host variables to the table. # # Parameters # dbproc contains all information needed by db-lib to manage communications with the server. # # Returns: # SUCCEED normal # FAIL on error # RETCODE bcp_sendrow (DBPROCESS *) nogil # Prepare for bulk copy operation on a table. # # Parameters # dbproc contains all information needed by db-lib to manage communications with the server. # tblname the name of the table receiving or providing the data. # hfile the data file opposite the table, if any. # errfile the "error file" captures messages and, if errors are encountered, copies of any rows that could not be written to the table. # direction one of # DB_IN writing to the table # DB_OUT writing to the host file # # Returns: # SUCCEED normal # FAIL on error RETCODE bcp_init (DBPROCESS *, const char *, const char *, const char *, int) nogil ## End Bulk Copy Functions ## ## Macros ## DBBOOL DBDEAD(DBPROCESS *) RETCODE DBSETLAPP(LOGINREC *x, char *y) RETCODE DBSETLHOST(LOGINREC *x, char *y) RETCODE DBSETLPWD(LOGINREC *x, char *y) RETCODE DBSETLUSER(LOGINREC *x, char *y) RETCODE DBSETLCHARSET(LOGINREC *x, char *y) RETCODE DBSETLVERSION(LOGINREC *login, BYTE version) RETCODE DBSETLDBNAME(LOGINREC *x, char *y) RETCODE DBSETLENCRYPT(LOGINREC *login, int x) ctypedef int LINE_T ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662366.0 pymssql-2.2.11/src/pymssql/version.h0000644000000000000000000000004000000000000017373 0ustar00rootroot00000000000000#define PYMSSQL_VERSION "2.2.11"././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.6004107 pymssql-2.2.11/src/pymssql.egg-info/0000755000000000000000000000000000000000000017235 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662366.0 pymssql-2.2.11/src/pymssql.egg-info/PKG-INFO0000644000000000000000000001125600000000000020337 0ustar00rootroot00000000000000Metadata-Version: 2.1 Name: pymssql Version: 2.2.11 Summary: DB-API interface to Microsoft SQL Server for Python. (new Cython-based version) Author: Damien Churchill Author-email: damoxc@gmail.com Maintainer: Mikhail Terekhov Maintainer-email: termim@gmail.com License: LGPL Project-URL: Documentation, http://pymssql.readthedocs.io Project-URL: Source, https://github.com/pymssql/pymssql Project-URL: Changelog, https://github.com/pymssql/pymssql/blob/master/ChangeLog.rst Keywords: mssql,SQL Server,database,DB-API Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Database Classifier: Topic :: Database :: Database Engines/Servers Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Description-Content-Type: text/x-rst License-File: LICENSE pymssql - DB-API interface to Microsoft SQL Server ================================================== .. image:: https://github.com/pymssql/pymssql/workflows/Linux/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22Linux%22 .. image:: https://github.com/pymssql/pymssql/workflows/macOS/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22macOS%22 .. image:: https://github.com/pymssql/pymssql/workflows/Windows/badge.svg :target: https://github.com/pymssql/pymssql/actions?query=workflow%3A%22Windows%22 .. image:: http://img.shields.io/pypi/dm/pymssql.svg :target: https://pypi.python.org/pypi/pymssql/ .. image:: http://img.shields.io/pypi/v/pymssql.svg :target: https://pypi.python.org/pypi/pymssql/ A simple database interface for `Python`_ that builds on top of `FreeTDS`_ to provide a Python DB-API (`PEP-249`_) interface to `Microsoft SQL Server`_. .. _Microsoft SQL Server: http://www.microsoft.com/sqlserver/ .. _Python: http://www.python.org/ .. _PEP-249: http://www.python.org/dev/peps/pep-0249/ .. _FreeTDS: http://www.freetds.org/ Detailed information on pymssql is available on the website: `pymssql.readthedocs.io `_ New development is happening on GitHub at: `github.com/pymssql/pymssql `_ There is a Google Group for discussion at: `groups.google.com `_ Getting started =============== pymssql wheels are available from PyPi. To install it run: .. code-block:: bash pip install -U pip pip install pymssql Most of the times this should be all what's needed. The official pymssql wheels bundle a static copy of FreeTDS and have SSL support so they can be used to connect to Azure. .. note:: On some Linux distributions `pip` version is too old to support all the flavors of manylinux wheels, so upgrading `pip` is necessary. An example of such distributions would be Ubuntu 18.04 or Python3.6 module in RHEL8 and CentOS8. Basic example ============= .. code-block:: python conn = pymssql.connect(server, user, password, "tempdb") cursor = conn.cursor(as_dict=True) cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe') for row in cursor: print("ID=%d, Name=%s" % (row['id'], row['name'])) conn.close() Recent Changes ============== Version 2.2.11 - 2023-12-03 - Mikhail Terekhov =============================================== General ------- - Use FreeTDS-1.4.9 for official wheels on PyPi. - Add workflow for aarch64 wheel. Thanks to juntangc (fix #692, #759, #791, #819, #826, #858). - Add datetime.date to SQLDATE conversion. - Add encription parameter to connect (fix #797). Bug fixes --------- - Fix version parsing in development. - Add missing `charset` parameter when formatting query (fix #650). - Use four digits for the year in SP args binding (fix #454). - Fix convert_python_value to work with datetime.date (fix #811). Version 2.2.10 - 2023-10-20 - Mikhail Terekhov =============================================== General ------- - Publish Linux wheels for Python-3.12 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662366.0 pymssql-2.2.11/src/pymssql.egg-info/SOURCES.txt0000644000000000000000000000424100000000000021122 0ustar00rootroot00000000000000.dockerignore .gitignore .pypirc .readthedocs.yaml ChangeLog.old ChangeLog.rst Dockerfile LICENSE MANIFEST.in README.rst appveyor.yml docker-compose.yml pyproject.toml pytest.ini setup.cfg setup.py tox.ini .github/dependabot.yml .github/ISSUE_TEMPLATE/bug_report.md .github/workflows/test_linux.yml .github/workflows/test_linux_aarch64.yml .github/workflows/test_macos.yml .github/workflows/test_windows.yml dev/__init__.py dev/build.py dev/build_freetds.sh dev/build_manylinux_wheels.sh dev/ccompiler.py dev/dev-requirements.pip dev/hudson.sh dev/memmonitor.py dev/memtest.py dev/memtest.sh dev/requirements-dev.txt dev/test_manylinux_wheels.sh dev/appveyor/install-win-iconv.ps1 dev/appveyor/install.ps1 dev/appveyor/pypirc dev/appveyor/run_with_env.cmd dev/appveyor/sql-server-activate-tcp-fixed-port.ps1 dev/appveyor/tests.cfg docs/Makefile docs/_mssql_examples.rst docs/azure.rst docs/building_and_developing.rst docs/changelog.rst docs/conf.py docs/docker.rst docs/faq.rst docs/freetds.rst docs/freetds_and_dates.rst docs/history.rst docs/index.rst docs/intro.rst docs/make.bat docs/migrate_1_x_to_2_x.rst docs/pymssql_examples.rst docs/release_notes.rst docs/requirements.txt docs/todo.rst docs/images/pymssql-stack.graphml docs/images/pymssql-stack.png docs/ref/_mssql.rst docs/ref/pymssql.rst src/pymssql/__init__.py src/pymssql/_mssql.pxd src/pymssql/_mssql.pyx src/pymssql/_pymssql.pyx src/pymssql/sqlfront.pxd src/pymssql/version.h src/pymssql.egg-info/PKG-INFO src/pymssql.egg-info/SOURCES.txt src/pymssql.egg-info/dependency_links.txt src/pymssql.egg-info/not-zip-safe src/pymssql.egg-info/top_level.txt tests/__init__.py tests/conftest.py tests/helpers.py tests/run_sqlalchemy_tests.py tests/test_bulk_copy.py tests/test_charset.py tests/test_config.py tests/test_connection_as_dict.py tests/test_connection_timeout.py tests/test_connections.py tests/test_context_managers.py tests/test_debug_queries.py tests/test_err_handle.py tests/test_green.py tests/test_memory.py tests/test_pymssql.py tests/test_queries.py tests/test_sprocs.py tests/test_sqlalchemy.py tests/test_threaded.py tests/test_types.py tests/test_unicode.py tests/test_user_msghandler.py tests/test_utils.py tests/tests.cfg.tpl././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662366.0 pymssql-2.2.11/src/pymssql.egg-info/dependency_links.txt0000644000000000000000000000000100000000000023303 0ustar00rootroot00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662246.0 pymssql-2.2.11/src/pymssql.egg-info/not-zip-safe0000644000000000000000000000000100000000000021463 0ustar00rootroot00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662366.0 pymssql-2.2.11/src/pymssql.egg-info/top_level.txt0000644000000000000000000000001000000000000021756 0ustar00rootroot00000000000000pymssql ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1701662366.6004107 pymssql-2.2.11/tests/0000755000000000000000000000000000000000000014406 5ustar00rootroot00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/__init__.py0000644000000000000000000000004000000000000016511 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/conftest.py0000644000000000000000000000717300000000000016615 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Pytest configuration. """ from os.path import dirname import sys # When using tox, it can accidentally pick up a {pymssql,_mssql}.so file in the # root directory and then get ImportError because of incompatibility in Python # versions. By removing the root directory from the sys.path, it forces tox to # import the library from correct place in the tox virtualenv. if '.tox' in sys.executable: root_dir = dirname(dirname(__file__)) sys.path.remove(root_dir) import decimal import os from configparser import ConfigParser import pytest import tests.helpers as th from .helpers import cfgpath, clear_db, get_app_lock, release_app_lock _parser = ConfigParser({ 'server': 'localhost', 'username': 'sa', 'password': 'sqlServerPassw0rd', 'database': 'tempdb', 'port': '1433', 'ipaddress': '127.0.0.1', 'instance': '', }) optional_markers = { "slow": {"help": "Skip long tests", "marker-descr": "Mark tests that run longer than ~3 seconds", "skip-reason": "Test runs too long."}, "mssql_server_required": {"help": "Skip tests that require MSSQL server", "marker-descr": "Mark tests that require MSSQL server", "skip-reason": "Test only runs if MSSQL server is available."}, # add further markers here } def pytest_addoption(parser): parser.addoption( "--pymssql-section", type=str, default=os.environ.get('PYMSSQL_TEST_CONFIG', 'DEFAULT'), help="The name of the section to use from tests.cfg" ) for marker, info in optional_markers.items(): parser.addoption("--skip-{}".format(marker.replace('_','-')), action="store_true", default=False, help=info['help']) def pytest_configure(config): _parser.read(cfgpath) section = config.getoption('--pymssql-section') if not _parser.has_section(section) and section != 'DEFAULT': raise ValueError('the tests.cfg file does not have section: %s' % section) th.config.server = os.getenv('PYMSSQL_TEST_SERVER') or _parser.get(section, 'server') th.config.user = os.getenv('PYMSSQL_TEST_USERNAME') or _parser.get(section, 'username') th.config.password = os.getenv('PYMSSQL_TEST_PASSWORD') or _parser.get(section, 'password') th.config.database = os.getenv('PYMSSQL_TEST_DATABASE') or _parser.get(section, 'database') th.config.port = os.getenv('PYMSSQL_TEST_PORT') or _parser.get(section, 'port') th.config.ipaddress = os.getenv('PYMSSQL_TEST_IPADDRESS') or _parser.get(section, 'ipaddress') th.config.instance = os.getenv('PYMSSQL_TEST_INSTANCE') or _parser.get(section, 'instance') th.config.orig_decimal_prec = decimal.getcontext().prec for marker, info in optional_markers.items(): config.addinivalue_line("markers", "{}: {}".format(marker, info['marker-descr'])) if get_app_lock(): clear_db() def pytest_unconfigure(config): release_app_lock() def pytest_collection_modifyitems(config, items): marker = "mssql_server_required" info = optional_markers[marker] if th.global_mssqlconn is None or config.getoption("--skip-{}".format(marker.replace('_','-'))): skip = pytest.mark.skip(reason=info['skip-reason']) for item in items: if marker in item.keywords: item.add_marker(skip) marker = "slow" info = optional_markers[marker] if th.global_mssqlconn is None or config.getoption("--skip-{}".format(marker)): skip = pytest.mark.skip(reason=info['skip-reason']) for item in items: if marker in item.keywords: item.add_marker(skip) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/helpers.py0000644000000000000000000003454200000000000016432 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Various test helper functions. """ import logging from os import path import time import pytest from pymssql import _mssql import pymssql def eq_(a, b): assert a == b mssql_server_required = pytest.mark.mssql_server_required class Config(object): def __str__(self): return f"server={self.server}, port={self.port}, database={self.database}, " \ f"user={self.user}, password={self.password}" config = Config() cdir = path.dirname(__file__) cfgpath = path.join(cdir, 'tests.cfg') global_mssqlconn = None def mssqlconn(conn_properties=None): return _mssql.connect( server=config.server, user=config.user, password=config.password, database=config.database, port=config.port, conn_properties=conn_properties ) def pymssqlconn(**kwargs): return pymssql.connect( server=config.server, user=config.user, password=config.password, database=config.database, port=config.port, **kwargs ) def get_app_lock(): global global_mssqlconn if global_mssqlconn is None: try: global_mssqlconn = mssqlconn() except Exception as exc: print(f"Could not connect to {config}:\n{exc}") return False t1 = time.time() while True: t2 = time.time() print("*** %d: Grabbing app lock for pymssql tests" % (t2,)) result = global_mssqlconn.execute_scalar(""" DECLARE @result INTEGER; EXEC @result = sp_getapplock @Resource = 'pymssql_tests', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = 60000; SELECT @result AS result; """) if result != -1: # -1 => timeout; keep looping break t2 = time.time() print( "*** %d: sp_getapplock for 'pymssql_tests' returned %d - " "it took %d seconds" % (t2, result, t2 - t1)) return True def release_app_lock(): if global_mssqlconn is None: return t1 = time.time() result = global_mssqlconn.execute_scalar(""" DECLARE @result INTEGER; EXEC @result = sp_releaseapplock @Resource = 'pymssql_tests', @LockOwner = 'Session'; SELECT @result AS result; """) print( "*** %d: sp_releaseapplock for 'pymssql_tests' returned %d" % (t1, result)) def drop_table(conn, tname): sql = "if object_id('%s') is not null drop table %s" % (tname, tname) conn.execute_non_query(sql) def clear_table(conn, tname): sql = 'delete from %s' % tname conn.execute_non_query(sql) class PyTableBase(object): tname = 'pymssql' cols = tuple() idtype = None @classmethod def table_sql(cls): return 'CREATE TABLE %s (%s)' % (cls.tname, ', '.join(cls.cols)) @classmethod def newconn(cls): cls.conn = pymssqlconn() @classmethod def setup_class(cls): cls.newconn() # table related commands managed by this class are handled in a # different connection cls._conn = mssqlconn() drop_table(cls._conn, cls.tname) cls._conn.execute_non_query(cls.table_sql()) def setUp(self): clear_table(self._conn, self.tname) def row_count(self): sql = 'select count(*) from %s' % self.tname return self.conn._conn.execute_scalar(sql) def execute(self, sql, params=None): cur = self.conn.cursor() cur.execute(sql, params) return cur class TableManager(object): def __init__(self, conn, tname, *cols): self.conn = conn self.tname = tname self.cols = cols self.create() def table_sql(self): return 'CREATE TABLE %s (%s)' % (self.tname, ', '.join(self.cols)) def drop(self): #mssql sql = "if object_id('%s') is not null drop table %s" % ( self.tname, self.tname) try: self.execute(sql) except Exception as e: self.conn.rollback() if 'syntax error' not in str(e): raise #sqlite sql = 'drop table if exists %s' % self.tname self.execute(sql) def execute(self, sql): cur = self.conn.cursor() cur.execute(sql) self.conn.commit() def create(self): self.drop() self.execute(self.table_sql()) def clear(self): sql = 'delete from %s' % self.tname self.execute(sql) def count(self): sql = 'select count(*) from %s' % self.tname cur = self.conn.cursor() cur.execute(sql) return cur.fetchone()[0] class DBAPIBase(object): def execute(self, sql): cur = self.conn.cursor() cur.execute(sql) return cur def executemany(self, sql, params_seq): cur = self.conn.cursor() cur.executemany(sql, params_seq) return cur class CursorBase(DBAPIBase): """ This is a "base" object because I have an uncommitted test module that runs these tests against psycopg to see what its behavior is. When psycopg comparison isn't needed anymore, this class can be moved to test_pymssql and used directly. """ @classmethod def setup_class(cls): cls.newconn() cls.t1 = TableManager(cls.conn, 'test', 'id int', 'name varchar(50)') def setup_method(self, method): self.conn.rollback() self.t1.clear() self.execute("insert into test values (1, 'one')") self.execute("insert into test values (2, 'two')") self.execute("insert into test values (3, 'three')") self.execute("insert into test values (4, 'four')") self.execute("insert into test values (5, 'five')") self.conn.commit() def test_description_not_used(self): cur = self.conn.cursor() assert cur.description is None def test_description_after_insert(self): cur = self.execute("insert into test values (6, 'six')") self.conn.commit() assert cur.description is None def test_description_after_select(self): cur = self.execute('select * from test') eq_(len(cur.description), 2) eq_(cur.description[0][0], 'id') eq_(self.dbmod.NUMBER, cur.description[0][1]) eq_(cur.description[1][0], 'name') eq_(self.dbmod.STRING, cur.description[1][1]) def test_sticky_description(self): cur = self.execute('select * from test') eq_(len(cur.description), 2) cur2 = self.execute('select id from test') eq_(len(cur2.description), 1) # description of first cursor should not be affected eq_(len(cur.description), 2) def test_fetchone(self): cur = self.execute('select * from test order by id') res = cur.fetchone() eq_(res[0], 1) res = cur.fetchone() eq_(res[0], 2) for x in range(0, 5): if cur.fetchone() is None: # make sure another call is also None and no exception is # raised assert cur.fetchone() is None break if x == 5: assert False, 'expected cur.fetchone() to be None' def test_insert_rowcount(self): cur = self.execute("insert into test values (6, 'six')") eq_(cur.rowcount, 1) self.conn.rollback() def test_delete_rowcount(self): cur = self.execute("delete from test where id = 5") eq_(cur.rowcount, 1) cur = self.execute("delete from test where id > 1") eq_(cur.rowcount, 3) self.conn.rollback() def test_update_rowcount(self): cur = self.execute("update test set name = 'foo' where id > 1") eq_(cur.rowcount, 4) self.conn.rollback() def test_select_rowcount(self): cur = self.execute('select * from test') eq_(cur.rowcount, -1) cur.fetchall() eq_(cur.rowcount, 5) def test_fetchone_rowcount(self): cur = self.execute('select * from test') eq_(cur.rowcount, -1) for _ in iter(cur.fetchone, None): eq_(cur.rowcount, -1) eq_(cur.rowcount, 5) def test_fetchmany_rowcount(self): cur = self.execute('select * from test') eq_(cur.rowcount, -1) for _ in iter(cur.fetchmany, []): eq_(cur.rowcount, -1) eq_(cur.rowcount, 5) def test_as_dict(self): # test for http://code.google.com/p/pymssql/issues/detail?id=92 cur = self.conn.cursor(as_dict=True) cur.execute("SELECT 'foo' AS first_name, 'bar' AS last_name") eq_(cur.fetchall(), [{'first_name': 'foo', 'last_name': 'bar'}]) def test_as_dict_no_column_name(self): cur = self.conn.cursor(as_dict=True) try: # SQL Server >= 2008: # # SELECT MAX(x), MIN(x) AS [MIN(x)] # FROM (VALUES (1), (2), (3)) # AS foo(x) # # SQL Server = 2005 (remove when we drop suport for it): # # SELECT MAX(x), MIN(x) AS [MIN(x)] # FROM (SELECT 1 # UNION ALL # SELECT 2 # UNION ALL # SELECT 3) # AS foo(x) cur.execute( "SELECT MAX(x), MIN(x) AS [MIN(x)] " "FROM (SELECT 1" " UNION ALL" " SELECT 2" " UNION ALL" " SELECT 3) AS foo(x)") assert False, "Didn't raise InterfaceError" except pymssql.ColumnsWithoutNamesError as exc: eq_(exc.columns_without_names, [0]) def test_as_dict_no_column_name_2(self): cur = self.conn.cursor(as_dict=True) try: # SQL Server >= 2008: # # SELECT MAX(x), MAX(y) AS [MAX(y)], MIN(y) # FROM (VALUES (1, 2), (2, 3), (3, 4)) # AS foo(x, y) # # SQL Server = 2005 (remove when we drop suport for it): # # SELECT MAX(x), MAX(y) AS [MAX(y)], MIN(y) # FROM (SELECT (1, 2) # UNION ALL # SELECT (2, 3) # UNION ALL # SELECT (3, 4)) # AS foo(x, y) cur.execute( "SELECT MAX(x), MAX(y) AS [MAX(y)], MIN(y) " "FROM (SELECT 1, 2" " UNION ALL" " SELECT 2, 3" " UNION ALL" " SELECT 3, 4) AS foo(x, y)") assert False, "Didn't raise InterfaceError" except pymssql.ColumnsWithoutNamesError as exc: eq_(exc.columns_without_names, [0, 2]) def test_fetchmany(self): cur = self.conn.cursor() cur.execute('select * from test') eq_(len(cur.fetchmany(2)), 2) eq_(len(cur.fetchmany(2)), 2) eq_(len(cur.fetchmany(2)), 1) # now a couple extra for good measure eq_(len(cur.fetchmany(2)), 0) eq_(len(cur.fetchmany(2)), 0) def test_execute_many(self): cur = self.executemany( "delete from test where id = %(id)s", [{'id': 1}, {'id': 2}]) self.conn.commit() eq_(self.t1.count(), 3) eq_(cur.rowcount, 2) def clear_db(): conn = mssqlconn() mapping = { 'P': 'drop procedure [%(name)s]', 'C': 'alter table [%(parent_name)s] drop constraint [%(name)s]', ('FN', 'IF', 'TF'): 'drop function [%(name)s]', 'V': 'drop view [%(name)s]', 'F': 'alter table [%(parent_name)s] drop constraint [%(name)s]', 'U': 'drop table [%(name)s]', } delete_sql = [] for type, drop_sql in mapping.items(): sql = 'select name, object_name( parent_object_id ) as parent_name '\ 'from sys.objects where type in (\'%s\')' % '", "'.join(type) conn.execute_query(sql) for row in conn: if row['name'][0] not in ('#','@'): delete_sql.append(drop_sql % dict(row)) for sql in delete_sql: conn.execute_non_query(sql) class StoredProc(object): def __init__(self, name, args, body, mssql=None): self.name = name self.args = args self.body = body self.mssql = mssql logger_name = '.'.join([__name__, self.__class__.__name__, self.name]) self.logger = logging.getLogger(logger_name) def create(self, mssql=None): mssql = mssql or self.mssql if not mssql: mssql = self.mssql = mssqlconn() try: self.drop(mssql) except: pass mssql.execute_non_query(""" CREATE PROCEDURE [dbo].[%(name)s] %(args)s AS BEGIN %(body)s END """ % { 'name': self.name, 'args': '\n'.join(self.args), 'body': self.body, }) self.logger.debug("Created stored proc: %r" % self.name) return self def execute(self, mssql=None, args=()): mssql = mssql or self.mssql if not mssql: mssql = self.mssql = mssqlconn() proc = mssql.init_procedure(self.name) for arg in args: proc.bind(*arg) self.logger.debug("Calling stored proc: %r" % self.name) proc.execute() self.logger.debug("Called stored proc: %r" % self.name) def drop(self, mssql=None): mssql = mssql or self.mssql if not mssql: mssql = self.mssql = mssqlconn() mssql.execute_non_query("DROP PROCEDURE [dbo].[%s]" % self.name) self.logger.debug("Dropped stored proc: %r" % self.name) if self.mssql: self.mssql.close() self.logger.debug("Closed mssql connection: %r" % self.mssql) self.mssql = None def __enter__(self): return self def __exit__(self, type, value, tb): self.drop() def get_sql_server_version(mssql_connection): """ Returns the version of the SQL Server in use: """ result = mssql_connection.execute_scalar( "SELECT CAST(SERVERPROPERTY('ProductVersion') as varchar)" ) ver_code = int(result.split('.')[0]) if ver_code >= 12: major_version = 2014 elif ver_code == 11: major_version = 2012 elif ver_code == 10: major_version = 2008 elif ver_code == 9: major_version = 2005 else: major_version = 2000 return major_version ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/run_sqlalchemy_tests.py0000755000000000000000000000357100000000000021241 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Run SQLAlchemy tests with pymssql connection. """ import configparser import os import sys import tarfile import urllib SQLALCHEMY_VERSION = "1.2.11" SQLALCHEMY_DIR = "SQLAlchemy-%s" % SQLALCHEMY_VERSION SQLALCHEMY_TAR_GZ = "%s.tar.gz" % SQLALCHEMY_DIR SQLALCHEMY_TAR_GZ_URL = "https://pypi.python.org/packages/source/S/SQLAlchemy/%s" % SQLALCHEMY_TAR_GZ def download_sqlalchemy_tarball(): sys.stdout.write('Downloading %s... ' % SQLALCHEMY_TAR_GZ_URL) sys.stdout.flush() urllib.urlretrieve(SQLALCHEMY_TAR_GZ_URL, SQLALCHEMY_TAR_GZ) sys.stdout.write('DONE\n') def extract_sqlalchemy_tarball(): tarball = tarfile.open(SQLALCHEMY_TAR_GZ, 'r:gz') sys.stdout.write('Extracting %s... ' % SQLALCHEMY_TAR_GZ) sys.stdout.flush() tarball.extractall('.') sys.stdout.write('DONE\n') def run_sqlalchemy_tests(): dburi = get_dburi() if dburi: sys.argv.append('--dburi=%s' % dburi) os.chdir('SQLAlchemy-%s' % SQLALCHEMY_VERSION) sys.path.append('.') sys.stdout.write('Running SQLAlchemy tests...\n\n') sys.stdout.flush() import sqla_nose def get_dburi(): config = configparser.SafeConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'tests.cfg')) username = config.get('DEFAULT', 'username') password = config.get('DEFAULT', 'password') server = config.get('DEFAULT', 'server') port = config.get('DEFAULT', 'port') database = config.get('DEFAULT', 'database') return 'mssql+pymssql://%(username)s:%(password)s@%(server)s:%(port)s/%(database)s' % dict( username=username, password=password, server=server, port=port, database=database) if not os.path.exists(SQLALCHEMY_TAR_GZ): download_sqlalchemy_tarball() if not os.path.exists(SQLALCHEMY_DIR): extract_sqlalchemy_tarball() run_sqlalchemy_tests() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_bulk_copy.py0000644000000000000000000001236700000000000020017 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test bulk copy. """ import unittest import datetime import pytest from pymssql import _mssql from tests.helpers import drop_table, pymssqlconn tablename = "pymssql" simple_table = "CREATE TABLE %s (a1 INT, a2 INT, a3 INT)" % tablename complex_table = """ CREATE TABLE %s ( pk_id int IDENTITY (1, 1) NOT NULL, uuid uniqueidentifier DEFAULT newsequentialid(), col_real real UNIQUE, col_float float, col_datetime datetime, col_bit bit, col_varchar varchar(50), col_varbinary varbinary(50) ) """ % tablename @pytest.mark.mssql_server_required class TestTypes(unittest.TestCase): def setUp(self): self.conn = pymssqlconn() drop_table(self.conn._conn, tablename) def tearDown(self): self.conn.close() def expect_simple_table_content(self, query, content): self.conn._conn.execute_query(query) assert [(row[0], row[1], row[2]) for row in self.conn._conn] == content def expect_row_count(self, expected_row_count): self.conn._conn.execute_query('select count(*) from pymssql') assert tuple(self.conn._conn)[0][0] == expected_row_count def simple_table_test(self, content, **kwargs): self.conn._conn.execute_non_query(simple_table) self.conn.bulk_copy(tablename, content, **kwargs) self.expect_simple_table_content('select * from pymssql', content) def test_simple_table_bulk_copy(self): self.simple_table_test([(1, 2, 3), (4, 5, 6)]) def test_lots_of_rows_single_batch(self): self.conn._conn.execute_non_query(simple_table) self.conn.bulk_copy(tablename, [(1, 2, 3), (4, 5, 6)] * 100000, batch_size=1000000) self.expect_simple_table_content('select top 2 * from pymssql', [(1, 2, 3), (4, 5, 6)]) self.expect_row_count(200000) def test_batches(self): self.conn._conn.execute_non_query(simple_table) self.conn.bulk_copy(tablename, [(1, 2, 3), (4, 5, 6)] * 100000, batch_size=1000) self.expect_simple_table_content('select top 2 * from pymssql', [(1, 2, 3), (4, 5, 6)]) self.expect_row_count(200000) def test_exact_batch_size(self): self.conn._conn.execute_non_query(simple_table) self.conn.bulk_copy(tablename, [(1, 2, 3), (4, 5, 6)] * 500, batch_size=1000) self.expect_simple_table_content('select top 2 * from pymssql', [(1, 2, 3), (4, 5, 6)]) self.expect_row_count(1000) def test_tablock_hint(self): self.simple_table_test([(1, 2, 3), (4, 5, 6)], tablock=True) def test_check_constraints_hint(self): self.simple_table_test([(1, 2, 3), (4, 5, 6)], check_constraints=True) def test_fire_triggers_hint(self): self.simple_table_test([(1, 2, 3), (4, 5, 6)], fire_triggers=True) def test_null_values(self): self.simple_table_test([(1, None, 3), (None, None, None), (1, 2, 3)]) def test_column_ids(self): self.conn._conn.execute_non_query(simple_table) self.conn.bulk_copy(tablename, [(1, 2, 3), (4, 5, 6)], column_ids=[1, 3, 2]) self.expect_simple_table_content('select * from pymssql', [(1, 3, 2), (4, 6, 5)]) def test_too_many_columns(self): self.conn._conn.execute_non_query(simple_table) with self.assertRaises(_mssql.MSSQLDatabaseException): self.conn.bulk_copy(tablename, [(7, 7, 7, 7)]) def test_bad_value(self): self.conn._conn.execute_non_query(simple_table) with self.assertRaises(_mssql.MSSQLDatabaseException): self.conn.bulk_copy(tablename, [("Hello", 7, 7)]) def test_too_few_column_ids(self): self.conn._conn.execute_non_query(simple_table) caught_exception = False try: self.conn.bulk_copy(tablename, [(1, 2, 3)], column_ids=[1]) except ValueError: caught_exception = True assert caught_exception def test_invalid_column_ids(self): self.conn._conn.execute_non_query(simple_table) with self.assertRaises(_mssql.MSSQLDatabaseException): self.conn.bulk_copy(tablename, [(1, 2, 3)], column_ids=[1, 2, 9]) def test_complex_table(self): self.conn._conn.execute_non_query(complex_table) rows = [ (1.2000000476837158, 3.4, datetime.datetime(year=2020, month=1, day=2, hour=3, minute=4, second=5), True, "Hello World", b'\x02\x03\x05\x07'), (5.599999904632568, 7.8, datetime.datetime(year=2021, month=2, day=3, hour=4, minute=5, second=6), False, "Hello World!", bytearray([2, 3, 5, 7])), ] self.conn.bulk_copy(tablename, rows, [3, 4, 5, 6, 7, 8]) self.conn._conn.execute_query('select * from pymssql') assert [tuple(row[i] for i in range(2, 8)) for row in self.conn._conn] == rows def test_uniqueness_failure(self): self.conn._conn.execute_non_query(complex_table) rows = [ (1.2000000476837158, 3.4, datetime.datetime(year=2020, month=1, day=2, hour=3, minute=4, second=5), True, "Hello World"), (1.2000000476837158, 7.8, datetime.datetime(year=2021, month=2, day=3, hour=4, minute=5, second=6), False, "Hello World!"), ] with self.assertRaises(_mssql.MSSQLDatabaseException): self.conn.bulk_copy(tablename, rows, [3, 4, 5, 6, 7]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_charset.py0000644000000000000000000000140000000000000017443 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test charset usage in queries. """ import unittest import pytest from .helpers import pymssqlconn @pytest.mark.mssql_server_required class TestCharset(unittest.TestCase): def setUp(self): self.conn = pymssqlconn(charset='WINDOWS-1251') def test_charset(self): cursor = self.conn.cursor() try: cursor.execute( 'select %s, %s', ('Здравствуй', 'Мир') # Russian strings ) except UnicodeDecodeError as e: self.fail("cursor.execute() raised %s unexpectedly: %s" % (e.__class__.__name__, e)) a, b = cursor.fetchone() self.assertEqual(a, 'Здравствуй') self.assertEqual(b, 'Мир') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_config.py0000644000000000000000000000754000000000000017272 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test connection config. """ from __future__ import with_statement from os import path, environ import tempfile import pytest from pymssql import _mssql @pytest.mark.mssql_server_required class TestConfig(object): def connect(self, **kwargs): with tempfile.TemporaryDirectory() as tmpdir: config_dump_path = path.join(tmpdir, 'freetds-config-dump.txt') environ['TDSDUMPCONFIG'] = config_dump_path try: _mssql.connect(**kwargs) assert False except _mssql.MSSQLDriverException as e: # we get this when the name of the server is not valid if 'Connection to the database failed' not in str(e): raise except _mssql.MSSQLDatabaseException as e: # we get this when the name or IP can be obtained but the connection # can not be made if e.args[0][0] != 20009: raise with open(config_dump_path, 'r') as fh: return fh.read() @pytest.mark.slow def test_config_values(self): config_dump = self.connect( server='dontnameyourserverthis', user = 'bob', database = 'tempdb', tds_version='7.1' ) assert 'user_name = bob' in config_dump assert 'database = tempdb\n' in config_dump # test default port assert 'port = 1433' in config_dump # not sure why 7.1 version is used instead of 8.0 which is the # default assert 'major_version = 7' in config_dump assert 'minor_version = 1' in config_dump @pytest.mark.slow def test_tds_protocol_version_42(self): config_dump = self.connect( server='dontnameyourserverthis', tds_version='4.2' ) assert 'major_version = 4' in config_dump assert 'minor_version = 2' in config_dump @pytest.mark.slow def test_tds_protocol_version_70(self): config_dump = self.connect( server='dontnameyourserverthis', tds_version='7.0' ) assert 'major_version = 7' in config_dump assert 'minor_version = 0' in config_dump @pytest.mark.slow def test_tds_protocol_version_71(self): config_dump = self.connect( server='dontnameyourserverthis', tds_version='7.1' ) assert 'major_version = 7' in config_dump assert 'minor_version = 1' in config_dump @pytest.mark.slow def test_tds_protocol_version_80(self): # follow-up: turns out 8.0 was erroneous. MS named the new protocol # 7.1 instead of 8.0, so FreeTDS will accept 8.0 but shows as 7.1. # got that from the FreeTDS mailling list. New FreeTDS docs,built from # source, have a page that describes the protocol and that page lists # versions 7.0, 7.1, and 7.2 among others. config_dump = self.connect( server='dontnameyourserverthis', tds_version='8.0' ) assert 'major_version = 7' in config_dump assert 'minor_version = 1' in config_dump @pytest.mark.slow def test_tds_protocol_version_72(self): config_dump = self.connect( server='dontnameyourserverthis', tds_version='7.2' ) assert 'major_version = 7' in config_dump assert 'minor_version = 2' in config_dump def test_tds_protocol_version_invalid(self): try: self.connect(tds_version='1.0') assert False except _mssql.MSSQLException as e: assert 'unrecognized tds version: 1.0' == str(e) def test_tds_nonstandard_port_int(self): #it should convert it to a string config_dump = self.connect(port=1435) assert 'port = 1435' in config_dump ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_connection_as_dict.py0000644000000000000000000000241600000000000021647 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test connection with as_dict=True. """ import unittest import pytest import pymssql from .helpers import config def pymssqlconn(**kwargs): return pymssql.connect( server=config.server, user=config.user, password=config.password, database=config.database, port=config.port, **kwargs ) @pytest.mark.mssql_server_required class TestConnectionAsDict(unittest.TestCase): def setUp(self): self.conn = pymssqlconn(as_dict=True) def test_fetchall_with_connection_as_dict(self): # This test is for http://code.google.com/p/pymssql/issues/detail?id=18 cursor = self.conn.cursor() cursor.execute("SELECT 'foo' AS first_name, 'bar' AS last_name") data = cursor.fetchall() self.assertEqual(data, [{'first_name': 'foo', 'last_name': 'bar'}]) def test_no_results_with_connection_as_dict(self): # Make sure that checking for columns without names doesn't break # statements that don't return results cursor = self.conn.cursor() cursor.execute(""" CREATE TABLE daily_measurement ( datetime DATETIME, value FLOAT, notes VARCHAR, ) """) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_connection_timeout.py0000644000000000000000000000116700000000000021731 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- import time import pytest import pymssql @pytest.mark.slow @pytest.mark.mssql_server_required @pytest.mark.timeout(120) @pytest.mark.xfail(strict=False) @pytest.mark.parametrize('to', [2]) def test_remote_connect_timeout(to): t = time.time() try: pymssql.connect(server="www.google.com", port=81, user='username', password='password', login_timeout=to) except pymssql.OperationalError: pass t = time.time() - t print('remote: requested {} -> {} actual timeout'.format(to, t)) assert t == pytest.approx(to, 5), "{} != {}".format(t, to) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_connections.py0000644000000000000000000001346300000000000020350 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test connection to database. """ from __future__ import with_statement from os import path, makedirs, environ import re import unittest import tempfile import pytest from pymssql import _mssql from .helpers import config, mssqlconn server = config.server username = config.user password = config.password database = config.database port = config.port ipaddress = config.ipaddress instance = config.instance @pytest.mark.mssql_server_required class TestCons(unittest.TestCase): def connect(self, **kwargs): with tempfile.TemporaryDirectory() as tmpdir: config_dump_path = path.join(tmpdir, 'freetds-config-dump.txt') dump_path = path.join(tmpdir, 'freetds-dump.txt') environ['TDSDUMPCONFIG'] = config_dump_path environ['TDSDUMP'] = dump_path _mssql.connect(**kwargs) with open(config_dump_path, 'r') as fh: return fh.read() def test_connection_by_dns_name(self): cdump = self.connect(server=server, port=port, user=username, password=password) dump_server_name = re.search('server_name = (\\S+)', cdump).groups()[0] self.assertIn(server, dump_server_name) dump_server_host_name = re.search('server_host_name = (\\S+)', cdump).groups()[0] self.assertEqual(dump_server_host_name, server) dump_user_name = re.search('user_name = (\\S+)', cdump).groups()[0] self.assertEqual(dump_user_name, username) dump_port = re.search('port = (\\S+)', cdump).groups()[0] self.assertIn(port, dump_port) def test_connection_by_ip(self): cdump = self.connect(server=ipaddress, port=port, user=username, password=password) dump_server_name = re.search('server_name = (\\S+)', cdump).groups()[0] self.assertIn(ipaddress, dump_server_name) dump_server_host_name = re.search('server_host_name = (\\S+)', cdump).groups()[0] self.assertEqual(dump_server_host_name, ipaddress) def test_port_override_ipaddress(self): server_join = '%s:%s' % (ipaddress, port) cdump = self.connect(server=server_join, user=username, password=password) dump_server_name = re.search('server_name = (\\S+)', cdump).groups()[0] self.assertIn(ipaddress, dump_server_name) dump_server_host_name = re.search('server_host_name = (\\S+)', cdump).groups()[0] self.assertEqual(dump_server_host_name, ipaddress) dump_port = re.search('port = (\\S+)', cdump).groups()[0] self.assertIn(port, dump_port) def test_port_override_name(self): server_join = '%s:%s' % (server, port) cdump = self.connect(server=server_join, user=username, password=password) dump_server_name = re.search('server_name = (\\S+)', cdump).groups()[0] self.assertIn(server, dump_server_name) dump_server_host_name = re.search('server_host_name = (\\S+)', cdump).groups()[0] self.assertEqual(dump_server_host_name, server) dump_port = re.search('port = (\\S+)', cdump).groups()[0] self.assertIn(port, dump_port) def test_instance(self): if not instance: pytest.skip() server_join = r'%s\%s' % (server, instance) cdump = self.connect(server=server_join, user=username, password=password) dump_server_name = re.search('server_name = (\\S+)', cdump).groups()[0] self.assertIn(server, dump_server_name) dump_server_host_name = re.search('server_host_name = (\\S+)', cdump).groups()[0] self.assertEqual(dump_server_host_name, server) dump_port = re.search('port = (\\S+)', cdump).groups()[0] self.assertEqual(dump_port, 0) def test_valid_tds_version_property(self): # Issue #211 (https://github.com/pymssql/pymssql/issues/211) conn = mssqlconn() self.assertIsNotNone(conn.tds_version) self.assertTrue(conn.tds_version > 0) conn.close() def test_conn_props_override(self): conn = mssqlconn(conn_properties='SET TEXTSIZE 2147483647') conn.close() conn = mssqlconn(conn_properties='SET TEXTSIZE 2147483647;') conn.close() conn = mssqlconn(conn_properties='SET TEXTSIZE 2147483647;SET ANSI_NULLS ON;') conn.close() conn = mssqlconn(conn_properties='SET TEXTSIZE 2147483647;SET ANSI_NULLS ON') conn.close() conn = mssqlconn(conn_properties='SET TEXTSIZE 2147483647;' 'SET ANSI_NULLS ON;') conn.close() conn = mssqlconn(conn_properties=['SET TEXTSIZE 2147483647;', 'SET ANSI_NULLS ON']) conn.close() self.assertRaises(_mssql.MSSQLDriverException, mssqlconn, conn_properties='BOGUS SQL') conn = _mssql.connect( conn_properties='SET TEXTSIZE 2147483647', server=server, user=username, password=password ) conn.close() @pytest.mark.slow class TestFailedConnection(unittest.TestCase): @pytest.mark.xfail(strict=False, reason="Could timeout, or fail with different error messages") @pytest.mark.timeout(600) def test_repeated_failed_connections(self): # This is a test for https://github.com/pymssql/pymssql/issues/145 # (Repeated failed connections result in error string getting longer # and longer) _mssql.login_timeout = 5 last_exc_message = None for i in range(5): try: _mssql.connect( server='www.google.com', port=81, user='joe', password='secret', database='tempdb') except Exception as exc: exc_message = exc.args[0][1] if last_exc_message: self.assertEqual(exc_message, last_exc_message) last_exc_message = exc_message ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_context_managers.py0000644000000000000000000000247400000000000021367 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test context managers -- i.e.: the `with` statement """ import unittest import pytest from pymssql import InterfaceError from .helpers import pymssqlconn, mssqlconn @pytest.mark.mssql_server_required class TestContextManagers(unittest.TestCase): def test_pymssql_Connection_with(self): with pymssqlconn() as conn: cursor = conn.cursor() cursor.execute("SELECT @@version AS version") self.assertIsNotNone(conn._conn) with self.assertRaises(InterfaceError) as context: self.assertIsNotNone(conn._conn) self.assertEqual(str(context.exception), "Connection is closed.") def test_pymssql_Cursor_with(self): conn = pymssqlconn() with conn.cursor() as cursor: cursor.execute("SELECT @@version AS version") self.assertIsNotNone(conn._conn) self.assertIsNotNone(cursor) with self.assertRaises(InterfaceError) as context: cursor.execute("SELECT @@version AS version") self.assertEqual(str(context.exception), "Cursor is closed.") def test_mssql_Connection_with(self): with mssqlconn() as conn: conn.execute_query("SELECT @@version AS version") self.assertTrue(conn.connected) self.assertFalse(conn.connected) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_debug_queries.py0000644000000000000000000000203400000000000020641 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test queries with debug on. """ from contextlib import contextmanager from io import StringIO import sys import unittest import pytest from .helpers import mssqlconn @contextmanager def redirect_stderr(): sys.stderr = StringIO() yield sys.stderr sys.stderr = sys.__stderr__ @pytest.mark.mssql_server_required class TestMSSQLConnectionWithDebugQueries(unittest.TestCase): def setUp(self): self.conn = mssqlconn() self.conn.debug_queries = True def test_MSSQLConnection_with_debug_queries(self): # This test is for http://code.google.com/p/pymssql/issues/detail?id=98 sql = "SELECT 'foo' AS first_name, 'bar' AS last_name" expected_row = { 0: 'foo', 1: 'bar', 'first_name': 'foo', 'last_name': 'bar', } with redirect_stderr() as stderr: row = self.conn.execute_row(sql) self.assertEqual(row, expected_row) self.assertEqual(stderr.getvalue(), "#%s#\n" % sql) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_err_handle.py0000644000000000000000000000466100000000000020131 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test error handling. """ from datetime import datetime import unittest import pytest from pymssql import _mssql @pytest.mark.mssql_server_required class ErrHandleTests(unittest.TestCase): def test01DBError(self): connection = None severity = 8 dberr = 101 oserr = 0 dberrstr = "toblerone1" oserrstr = None expect = "DB-Lib error message %d, severity %d:\n%s\n" % ( dberr, severity, dberrstr) expect = expect.encode('UTF-8') values = _mssql.test_err_handler( connection, severity, dberr, oserr, dberrstr, oserrstr) self.assertEqual(values[0], 2) self.assertEqual(values[1], expect) def test02OSError(self): connection = None # EXCOMM severity = 9 dberr = 102 oserr = 1001 dberrstr = "toblerone2" oserrstr = "scorpion" expect = ( "DB-Lib error message %d, severity %d:\n%s\n" "Net-Lib error during %s (%d)\n" % ( dberr, severity, dberrstr, oserrstr, oserr)) expect = expect.encode('UTF-8') values = _mssql.test_err_handler( connection, severity, dberr, oserr, dberrstr, oserrstr) self.assertEqual(values[0], 2) self.assertEqual(values[1], expect) def test03OSError(self): connection = None severity = 10 dberr = 103 oserr = 1003 dberrstr = "toblerone3" oserrstr = "cabezon" expect = ( "DB-Lib error message %d, severity %d:\n%s\n" "Operating System error during %s (%d)\n" % ( dberr, severity, dberrstr, oserrstr, oserr)) expect = expect.encode('UTF-8') values = _mssql.test_err_handler( connection, severity, dberr, oserr, dberrstr, oserrstr) self.assertEqual(values[0], 2) self.assertEqual(values[1], expect) def test04NoError(self): connection = None # smaller than min error severity, so no output should be generated severity = 5 dberr = 10 oserr = 4444 dberrstr = "toblerone4" oserrstr = "limpet" expect = b"" values = _mssql.test_err_handler( connection, severity, dberr, oserr, dberrstr, oserrstr) self.assertEqual(values[0], 2) self.assertEqual(values[1], expect) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_green.py0000644000000000000000000001056500000000000017126 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Some async tests with gevent. """ import datetime import unittest import pytest import pymssql from .helpers import mssqlconn, pymssqlconn gevent = pytest.importorskip("gevent") try: import gevent.socket except ImportError: pytest.skip('gevent is not available', allow_module_level=True) @pytest.mark.mssql_server_required class GreenletTests(unittest.TestCase): def greenlet_run_pymssql_execute(self, num): with pymssqlconn() as conn: cur = conn.cursor() cur.execute(""" WAITFOR DELAY '00:00:05' -- sleep for 5 seconds SELECT CURRENT_TIMESTAMP """) row = cur.fetchone() print("greenlet_run_pymssql_execute: num = %r; row = %r" % (num, row)) def greenlet_run_pymssql_callproc(self, num): with pymssqlconn() as conn: cur = conn.cursor() proc_name = 'my_proc' # print("~~ Checking for stored proc (num=%r)..." % num) # cur.execute("IF OBJECT_ID('%s', 'P') IS NOT NULL DROP PROCEDURE %s" % (proc_name, proc_name)) # print("~~ Creating stored proc (num=%r)..." % num) # cur.execute(""" # CREATE PROCEDURE %s AS # BEGIN # SET NOCOUNT ON # WAITFOR DELAY '00:00:05' -- sleep for 5 seconds # SELECT CURRENT_TIMESTAMP # END # """ % (proc_name,)) # print("~~ Calling stored proc (num=%r)..." % num) cur.callproc(proc_name, ()) # print("~~ callproc returned (num=%r)..." % num) cur.nextset() row = cur.fetchone() print("greenlet_run_pymssql_callproc: num = %r; row = %r" % (num, row)) cur.close() def greenlet_run_mssql_execute(self, num): conn = mssqlconn() conn.execute_query(""" WAITFOR DELAY '00:00:05' -- sleep for 5 seconds SELECT CURRENT_TIMESTAMP """) for row in conn: print("greenlet_run_mssql_execute: num = %r; row = %r" % (num, row)) pass conn.close() def _run_all_greenlets(self, greenlet_task): greenlets = [] dt1 = datetime.datetime.now() for i in range(5): gevent.sleep(1) greenlets.append(gevent.spawn(greenlet_task, i)) gevent.joinall(greenlets) dt2 = datetime.datetime.now() return dt2 - dt1 @pytest.mark.slow def test_gevent_socket_pymssql_execute_wait_read_concurrency(self): def wait_callback(read_fileno): gevent.socket.wait_read(read_fileno) pymssql.set_wait_callback(wait_callback) elapsed_time = self._run_all_greenlets( self.greenlet_run_pymssql_execute) self.assertTrue( elapsed_time < datetime.timedelta(seconds=20), 'elapsed_time < 20 seconds') @pytest.mark.slow def test_gevent_socket_pymssql_callproc_wait_read_concurrency(self): def wait_callback(read_fileno): gevent.socket.wait_read(read_fileno) pymssql.set_wait_callback(wait_callback) with pymssqlconn() as conn: cur = conn.cursor() proc_name = 'my_proc' cur.execute("IF OBJECT_ID('%s', 'P') IS NOT NULL DROP PROCEDURE %s" % (proc_name, proc_name)) cur.execute(""" CREATE PROCEDURE %s AS BEGIN SET NOCOUNT ON WAITFOR DELAY '00:00:05' -- sleep for 5 seconds SELECT CURRENT_TIMESTAMP END """ % (proc_name,)) conn.commit() elapsed_time = self._run_all_greenlets( self.greenlet_run_pymssql_callproc) self.assertTrue( elapsed_time < datetime.timedelta(seconds=20), 'elapsed_time < 20 seconds') @pytest.mark.slow def test_gevent_socket_mssql_execute_wait_read_concurrency(self): def wait_callback(read_fileno): gevent.socket.wait_read(read_fileno) pymssql.set_wait_callback(wait_callback) elapsed_time = self._run_all_greenlets( self.greenlet_run_mssql_execute) self.assertTrue( elapsed_time < datetime.timedelta(seconds=20), 'elapsed_time < 20 seconds') suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(GreenletTests)) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_memory.py0000644000000000000000000000204300000000000017326 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- import gc import sys import psutil import pymssql import pytest @pytest.mark.slow @pytest.mark.xfail(reason="Memory test is not stable") def test_memory_leak_on_unsuccessful_connect(): """ This test checks python process memory usage and not just unsuccessful connect path. Many other factors (i.e. garbage collection) affect memory usage pattern. So this is an indirect test which is somewhat flaky and because of that is marked 'xfail'. """ p = psutil.Process() m0 = p.memory_full_info() for i in range(10): gc.collect() try: pymssql.connect(server="www.google.com", port=81, user='username', password='password', login_timeout=1) except: pass gc.collect() m1 = p.memory_full_info() duss = m1.uss - m0.uss print(i, "uss=", m1.uss, "duss:", duss) if i > 5: assert duss <= 0 m0 = m1 if __name__ == '__main__': test_memory_leak_on_unsuccessful_connect() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_pymssql.py0000644000000000000000000001623100000000000017532 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test pymssql module. """ import unittest import pytest import pymssql as pym from .helpers import pymssqlconn, PyTableBase, CursorBase, eq_, config class TestDBAPI2(object): def test_version(self): assert pym.__version__ @pytest.mark.mssql_server_required class TestTransaction(unittest.TestCase, PyTableBase): tname = 'users' cols = ( 'name varchar(50)', ) def setUp(self): PyTableBase.setUp(self) # make sure we start with a fresh connection for transaction tests # so that previous transaction activities don't taint a test self.newconn() def test_immediate_rollback(self): # just making sure this doesn't throw an exception self.conn.rollback() def test_multiple_rollbacks(self): # just making sure this doesn't throw an exception self.conn.rollback() self.conn.rollback() self.conn.rollback() def test_rollback(self): cur = self.conn.cursor() cur.execute('insert into users values (%s)', 'foobar') eq_(self.row_count(), 1) self.conn.rollback() eq_(self.row_count(), 0) def test_commit(self): cur = self.conn.cursor() cur.execute('insert into users values (%s)', 'foobar') eq_(self.row_count(), 1) self.conn.commit() self.conn.rollback() eq_(self.row_count(), 1) def test_rollback_after_error(self): cur = self.conn.cursor() cur.execute('insert into users values (%s)', 'foobar') eq_(self.row_count(), 1) try: cur.execute('insert into notable values (%s)', '123') except pym.ProgrammingError as e: if 'notable' not in str(e): raise # encountered an error, so we want to rollback self.conn.rollback() # rollback should have resulted in user's insert getting rolled back # too eq_(self.row_count(), 0) def test_rollback_after_create_error(self): """ test_rollback_after_create_error For some reason, SQL server will issue a batch-abort if the statement is a CREATE statement and it fails. This means the transaction is implicitly rolled back and a subsequent call to rollback() without special handling would result in an error. """ cur = self.conn.cursor() cur.execute('insert into users values (%s)', 'foobar') eq_(self.row_count(), 1) try: cur.execute("CREATE TABLE badschema.t1 ( test1 CHAR(5) NOT NULL)") except pym.OperationalError as e: if 'badschema' not in str(e): raise # encountered an error, so we want to rollback self.conn.rollback() # rollback should have resulted in user's insert getting rolled back # too eq_(self.row_count(), 0) @pytest.mark.mssql_server_required class TestCursor(CursorBase): dbmod = pym @classmethod def newconn(cls): cls.conn = pymssqlconn() @pytest.mark.mssql_server_required class TestBasicConnection(unittest.TestCase): def connect(self, conn_props=None): return pym.connect( server=config.server, user=config.user, password=config.password, database=config.database, port=config.port, conn_properties=conn_props ) def test_conn_props_override(self): conn = self.connect(conn_props='SET TEXTSIZE 2147483647') conn.close() conn = self.connect(conn_props='SET TEXTSIZE 2147483647;') conn.close() conn = self.connect(conn_props='SET TEXTSIZE 2147483647;SET ANSI_NULLS ON;') conn.close() conn = self.connect(conn_props='SET TEXTSIZE 2147483647;SET ANSI_NULLS ON') conn.close() conn = self.connect(conn_props='SET TEXTSIZE 2147483647;' 'SET ANSI_NULLS ON;') conn.close() conn = self.connect(conn_props=['SET TEXTSIZE 2147483647;', 'SET ANSI_NULLS ON']) conn.close() self.assertRaises(Exception, self.connect, conn_props='BOGUS SQL') conn = pym.connect( conn_properties='SET TEXTSIZE 2147483647', server=config.server, user=config.user, password=config.password ) conn.close() @pytest.mark.mssql_server_required class TestAutocommit(unittest.TestCase, PyTableBase): tname = 'test' cols = ( 'name varchar(50)', ) insert_query = 'INSERT INTO {tname} VALUES (%s)'.format(tname=tname) select_query = 'SELECT * FROM {tname} WHERE name = (%s)'.format(tname=tname) test_db_name = 'autocommit_test_database' def setUp(self): PyTableBase.setUp(self) def tearDown(self): self.conn._conn.execute_non_query("IF EXISTS(select * from sys.databases where name='{0}') DROP DATABASE {0}".format(self.test_db_name)) def test_db_creation_with_autocommit(self): """ Try creating and dropping database with autocommit """ cur = pymssqlconn(autocommit=True).cursor() try: cur.execute("CREATE DATABASE {0}".format(self.test_db_name)) except pym.OperationalError as e: expected_msg = "CREATE DATABASE permission denied in database 'master'" if expected_msg in str(e.args[1]): pytest.skip('We have no CREATE DATABASE permission on test database') else: pytest.fail() else: cur.execute("DROP DATABASE {0}".format(self.test_db_name)) def test_db_creation_without_autocommit(self): """ Try creating and dropping database without autocommit, expecting it to fail """ cur = pymssqlconn(autocommit=False).cursor() with pytest.raises(pym.OperationalError) as excinfo: cur.execute("CREATE DATABASE autocommit_test_database") expected_msg = "CREATE DATABASE statement not allowed within multi-statement transaction" assert expected_msg in excinfo.exconly() def test_autocommit_flipping_tf(self): insert_value = 'true-false' conn = pymssqlconn(autocommit=True) conn.autocommit(False) cur = conn.cursor() cur.execute(self.insert_query, insert_value) conn.commit() cur.execute(self.select_query, insert_value) row = cur.fetchone() cur.close() conn.close() assert len(row) > 0 def test_autocommit_flipping_ft(self): insert_value = 'false-true' conn = pymssqlconn(autocommit=False) conn.autocommit(True) cur = conn.cursor() cur.execute(self.insert_query, insert_value) cur.execute(self.select_query, insert_value) row = cur.fetchone() assert len(row) > 0 def test_autocommit_false_does_not_commit(self): insert_value = 'false' conn = pymssqlconn(autocommit=False) cur = conn.cursor() cur.execute(self.insert_query, insert_value) conn.rollback() cur.execute(self.select_query, insert_value) row = cur.fetchone() cur.close() conn.close() assert row is None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_queries.py0000644000000000000000000000727400000000000017506 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test queries. """ from datetime import datetime import unittest import pytest from pymssql import _mssql from .helpers import mssqlconn, drop_table @pytest.mark.mssql_server_required class QueryTests(unittest.TestCase): @classmethod def setup_class(cls): cls.mssql = mssqlconn() cls.createTestTable() @classmethod def teardown_class(cls): cls.dropTestTable() cls.mssql.close() @classmethod def createTestTable(cls): try: cls.mssql.execute_non_query(""" CREATE TABLE pymssql ( pk_id int IDENTITY (1, 1) NOT NULL, real_no real, float_no float, money_no money, stamp_datetime datetime, data_bit bit, comment_vch varchar(50), comment_nvch nvarchar(50), comment_text text, comment_ntext ntext, data_image image, data_binary varbinary(40), decimal_no decimal(38,2), numeric_no numeric(38,8), stamp_time timestamp, bin_data varbinary(16) )""") cls.tableCreated = True cls.testTableColCount = 16 except _mssql.MSSQLDatabaseException as e: if e.number != 2714: raise @classmethod def dropTestTable(cls): cls.mssql.execute_non_query('DROP TABLE pymssql') cls.tableCreated = False def insertSampleData(self): for x in range(10): y = x + 1 query = """ INSERT INTO pymssql ( real_no, float_no, money_no, stamp_datetime, data_bit, comment_vch, comment_ntext, comment_text, comment_nvch, decimal_no, numeric_no, bin_data ) VALUES ( %d, %d, %d, getdate(), %d, 'comment %d', 'detail %d', 'hmm', 'bhmme', 234.99, 894123.09, %#x );""" % (y, y, y, (y % 2), y, y, y) self.mssql.execute_non_query(query) def test01SimpleSelect(self): query = 'SELECT getdate() as cur_date_info' self.mssql.execute_query(query) rows = tuple(self.mssql) self.assertTrue(isinstance(rows[0]['cur_date_info'], datetime)) def test02EmptySelect(self): query = 'SELECT * FROM pymssql' self.mssql.execute_query(query) rows = tuple(self.mssql) self.assertEqual(rows, ()) def test03InsertSelect(self): self.insertSampleData() self.mssql.execute_query('SELECT * FROM pymssql') # check row count rows = tuple(self.mssql) self.assertEqual(10, len(rows)) # check col count cols = [k for k in rows[0] if type(k) is int] self.assertEqual(self.testTableColCount, len(cols)) def test19MultipleResults(self): self.mssql.execute_query("SELECT 'ret1'; SELECT 'ret2'; SELECT 'ret3'") rows = tuple(self.mssql) self.assertEqual(rows[0][0], 'ret1') self.mssql.nextresult() rows = tuple(self.mssql) self.assertEqual(rows[0][0], 'ret2') self.mssql.nextresult() rows = tuple(self.mssql) self.assertEqual(rows[0][0], 'ret3') def test04BinaryTypeSqlInjection(self): self.mssql.execute_query('SELECT * FROM pymssql WHERE bin_data=%s', ('0x OR 1=1;',)) rows = tuple(self.mssql) self.assertEqual(len(rows), 0) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_sprocs.py0000644000000000000000000004650600000000000017343 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test stored procedure usage. """ import decimal import datetime import os import sys import unittest import pymssql from pymssql import _mssql import pytest from .helpers import mssqlconn, pymssqlconn FIXED_TYPES = ( 'BigInt', 'Bit', 'DateTime', 'Decimal', 'Int', 'Money', 'Numeric', 'SmallInt', 'TinyInt', 'UniqueIdentifier', 'Real', 'Float' ) VARIABLE_TYPES = ( ('Char', 4), ('VarChar', 4), ('VarBinary', 4), ('Text', None) # Leave this one in the last position in case it fails (see https://code.google.com/p/pymssql/issues/detail?id=113#c2) ) @pytest.mark.mssql_server_required class TestFixedTypeConversion(unittest.TestCase): def setUp(self): self.mssql = mssqlconn() self.pymssql = pymssqlconn() for name in FIXED_TYPES: dbtype = name.lower() if dbtype == 'decimal': identifier = 'decimal(6, 5)' elif dbtype == 'numeric': identifier = 'numeric(6, 5)' else: identifier = dbtype self.mssql.execute_non_query(""" CREATE PROCEDURE [dbo].[pymssqlTest%(name)s] @i%(dbtype)s %(identifier)s, @o%(dbtype)s %(identifier)s output AS BEGIN SET @o%(dbtype)s = @i%(dbtype)s; RETURN 0; END """ % { 'dbtype': dbtype, 'name': name, 'identifier': identifier }) def tearDown(self): for name in FIXED_TYPES: self.mssql.execute_non_query('DROP PROCEDURE [dbo].[pymssqlTest%s]' % name) self.pymssql.close() self.mssql.close() def testBigInt(self): input = 123456789 proc = self.mssql.init_procedure('pymssqlTestBigInt') proc.bind(input, _mssql.SQLINT8, '@ibigint') proc.bind(None, _mssql.SQLINT8, '@obigint', output=True) proc.execute() self.assertEqual(input, proc.parameters['@obigint']) def testBigIntPymssql(self): """Same as testBigInt above but from pymssql. Uses pymssql.output class.""" if sys.version_info >= (3, ): py_type = int else: py_type = long in_val = 123456789 cursor = self.pymssql.cursor() retval = cursor.callproc('pymssqlTestBigInt', [in_val, pymssql.output(py_type)]) self.assertEqual(in_val, retval[1]) in_val = 2147483647 retval = cursor.callproc('pymssqlTestBigInt', [in_val, pymssql.output(py_type)]) self.assertEqual(in_val, retval[1]) def testBit(self): input = True proc = self.mssql.init_procedure('pymssqlTestBit') proc.bind(input, _mssql.SQLBITN, '@ibit') proc.bind(False, _mssql.SQLBITN, '@obit', output=True) proc.execute() self.assertEqual(input, proc.parameters['@obit']) def testDateTime(self): input = datetime.datetime(2009, 8, 27, 15, 28, 38) proc = self.mssql.init_procedure('pymssqlTestDateTime') proc.bind(input, _mssql.SQLDATETIME, '@idatetime') proc.bind(None, _mssql.SQLDATETIME, '@odatetime', output=True) proc.execute() self.assertEqual(input, proc.parameters['@odatetime']) def testDecimal(self): input = decimal.Decimal('5.12345') output = decimal.Decimal('0.00000') proc = self.mssql.init_procedure('pymssqlTestDecimal') proc.bind(input, _mssql.SQLDECIMAL, '@idecimal') proc.bind(output, _mssql.SQLDECIMAL, '@odecimal', output=True, max_length=6) proc.execute() self.assertEqual(input, proc.parameters['@odecimal']) self.assertEqual(str(input), str(proc.parameters['@odecimal'])) def testDecimal2(self): input = decimal.Decimal('6.23456') output = decimal.Decimal('0.00000') proc = self.mssql.init_procedure('pymssqlTestDecimal') proc.bind(input, _mssql.SQLDECIMAL, '@idecimal') proc.bind(output, _mssql.SQLDECIMAL, '@odecimal', output=True, max_length=6) proc.execute() self.assertEqual(input, proc.parameters['@odecimal']) self.assertEqual(str(input), str(proc.parameters['@odecimal'])) def testDecimal3(self): output = decimal.Decimal('0.00000') proc = self.mssql.init_procedure('pymssqlTestDecimal') input = decimal.Decimal('6.23400') proc.bind(input, _mssql.SQLDECIMAL, '@idecimal') proc.bind(output, _mssql.SQLDECIMAL, '@odecimal', output=True, max_length=6) proc.execute() self.assertEqual(input, proc.parameters['@odecimal']) self.assertEqual(str(input), str(proc.parameters['@odecimal'])) def testDecimal4(self): output = decimal.Decimal('1.0000000') proc = self.mssql.init_procedure('pymssqlTestDecimal') input = decimal.Decimal('6.2340000') proc.bind(input, _mssql.SQLDECIMAL, '@idecimal') proc.bind(output, _mssql.SQLDECIMAL, '@odecimal', output=True, max_length=15) proc.execute() self.assertEqual(input, proc.parameters['@odecimal']) self.assertEqual(str(input), str(proc.parameters['@odecimal'])) def testDecimal5(self): output = decimal.Decimal('1.000000000') proc = self.mssql.init_procedure('pymssqlTestDecimal') input = decimal.Decimal('6.234000000') proc.bind(input, _mssql.SQLDECIMAL, '@idecimal') proc.bind(output, _mssql.SQLDECIMAL, '@odecimal', output=True, max_length=15) proc.execute() self.assertEqual(input, proc.parameters['@odecimal']) self.assertEqual(str(input), str(proc.parameters['@odecimal'])) def testInt(self): input = 10056 proc = self.mssql.init_procedure('pymssqlTestInt') proc.bind(input, _mssql.SQLINT4, '@iint') proc.bind(None, _mssql.SQLINT4, '@oint', output=True) proc.execute() self.assertEqual(input, proc.parameters['@oint']) def testMoney(self): input = decimal.Decimal('5.12') proc = self.mssql.init_procedure('pymssqlTestMoney') proc.bind(input, _mssql.SQLMONEY, '@imoney') proc.bind(None, _mssql.SQLMONEY, '@omoney', output=True) proc.execute() self.assertEqual(input, proc.parameters['@omoney']) def testNumeric(self): input = decimal.Decimal('5.12345') output = decimal.Decimal('0.00000') proc = self.mssql.init_procedure('pymssqlTestNumeric') proc.bind(input, _mssql.SQLNUMERIC, '@inumeric') proc.bind(output, _mssql.SQLNUMERIC, '@onumeric', output=True) proc.execute() self.assertEqual(input, proc.parameters['@onumeric']) self.assertEqual(str(input), str(proc.parameters['@onumeric'])) def testSmallInt(self): input = 10056 proc = self.mssql.init_procedure('pymssqlTestSmallInt') proc.bind(input, _mssql.SQLINT2, '@ismallint') proc.bind(None, _mssql.SQLINT2, '@osmallint', output=True) proc.execute() self.assertEqual(input, proc.parameters['@osmallint']) def testTinyInt(self): input = 101 proc = self.mssql.init_procedure('pymssqlTestTinyInt') proc.bind(input, _mssql.SQLINT1, '@itinyint') proc.bind(None, _mssql.SQLINT1, '@otinyint', output=True) proc.execute() self.assertEqual(input, proc.parameters['@otinyint']) def testUuid(self): if os.environ.get('FREETDS_VERSION') != '0.91': pytest.skip("UNIQUEIDENTIFIER as a SP param doesn't work with FreeTDS >= 0.95") import uuid input = uuid.uuid4() proc = self.mssql.init_procedure('pymssqlTestUniqueIdentifier') proc.bind(input, _mssql.SQLUUID, '@iuniqueidentifier') proc.bind(None, _mssql.SQLUUID, '@ouniqueidentifier', output=True) proc.execute() self.assertEqual(input, proc.parameters['@ouniqueidentifier']) def testReal(self): input = 3.14 proc = self.mssql.init_procedure('pymssqlTestReal') proc.bind(input, _mssql.SQLREAL, '@ireal') proc.bind(None, _mssql.SQLREAL, '@oreal', output=True) proc.execute() assert abs(input - proc.parameters['@oreal']) < 0.00001 def testFloat8(self): input = 3.40E38 + 1 proc = self.mssql.init_procedure('pymssqlTestFloat') proc.bind(input, _mssql.SQLFLT8, '@ifloat') proc.bind(None, _mssql.SQLFLT8, '@ofloat', output=True) proc.execute() assert abs(input - proc.parameters['@ofloat']) < 0.00001 @pytest.mark.mssql_server_required class TestCallProcFancy(unittest.TestCase): # "Fancy" because we test some exotic cases like passing None or Unicode # strings to a called procedure def setUp(self): self.pymssql = pymssqlconn() cursor = self.pymssql.cursor() sql = u""" CREATE PROCEDURE [dbo].[someProcWithOneParam] @some_arg NVARCHAR(64) AS BEGIN SELECT @some_arg + N'!', N'%(str1)s ' + @some_arg + N' %(str2)s' END """ % { 'str1': 'Здравствуй', 'str2': 'Мир', } sql = sql.encode('utf-8') cursor.execute(sql) def tearDown(self): cursor = self.pymssql.cursor() cursor.execute('DROP PROCEDURE [dbo].[someProcWithOneParam]') self.pymssql.close() def testCallProcWithNone(self): cursor = self.pymssql.cursor() cursor.callproc( 'someProcWithOneParam', (None,)) a, b = cursor.fetchone() self.assertEqual(a, None) self.assertEqual(b, None) def testCallProcWithAsciiString(self): cursor = self.pymssql.cursor() cursor.callproc( 'someProcWithOneParam', ('hello',)) a, b = cursor.fetchone() self.assertEqual(a, 'hello!') self.assertEqual(b, 'Здравствуй hello Мир') def testCallProcWithUnicodeStringWithNoFunnyCharacters(self): cursor = self.pymssql.cursor() cursor.callproc( 'someProcWithOneParam', ('hello',)) a, b = cursor.fetchone() self.assertEqual(a, 'hello!') self.assertEqual(b, 'Здравствуй hello Мир') # This is failing for me - the Unicode params somehow gets rendered to a # blank string. I am not sure if this is another bug or a user error on my # part...? # def testCallProcWithUnicodeStringWithRussianCharacters(self): cursor = self.pymssql.cursor() cursor.callproc( 'someProcWithOneParam', ('Здравствуй',)) # Russian string a, b = cursor.fetchone() self.assertEqual(a, 'Здравствуй!') self.assertEqual(b, 'Здравствуй Здравствуй Мир') def testExecuteWithNone(self): cursor = self.pymssql.cursor() cursor.execute( 'someProcWithOneParam %s', (None,)) a, b = cursor.fetchone() self.assertEqual(a, None) self.assertEqual(b, None) def testExecuteWithAsciiString(self): cursor = self.pymssql.cursor() cursor.execute( 'someProcWithOneParam %s', ('hello',)) a, b = cursor.fetchone() self.assertEqual(a, 'hello!') self.assertEqual(b, 'Здравствуй hello Мир') def testExecuteWithUnicodeStringWithNoFunnyCharacters(self): cursor = self.pymssql.cursor() cursor.execute( 'someProcWithOneParam %s', ('hello',)) a, b = cursor.fetchone() self.assertEqual(a, 'hello!') self.assertEqual(b, 'Здравствуй hello Мир') def testExecuteWithUnicodeWithRussianCharacters(self): cursor = self.pymssql.cursor() cursor.execute( 'someProcWithOneParam %s', ('Здравствуй',)) # Russian string a, b = cursor.fetchone() self.assertEqual(a, 'Здравствуй!') self.assertEqual(b, 'Здравствуй Здравствуй Мир') @pytest.mark.mssql_server_required class TestStringTypeConversion(unittest.TestCase): def setUp(self): self.mssql = mssqlconn() for name, size in VARIABLE_TYPES: dbtype = name.lower() identifier = dbtype if dbtype == 'text' else '%s(%d)' % (dbtype, size) try: self.mssql.execute_non_query(""" CREATE PROCEDURE [dbo].[pymssqlTest%(name)s] @i%(dbtype)s %(identifier)s, @o%(dbtype)s %(identifier)s output AS BEGIN SET @o%(dbtype)s = @i%(dbtype)s; RETURN 0; END """ % { 'dbtype': dbtype, 'name': name, 'identifier': identifier }) except: if name == 'Text': raise def tearDown(self): for name, size in VARIABLE_TYPES: self.mssql.execute_non_query('DROP PROCEDURE [dbo].[pymssqlTest%s]' % name) self.mssql.close() def testChar(self): input = 'test' proc = self.mssql.init_procedure('pymssqlTestChar') proc.bind(input, _mssql.SQLCHAR, '@ichar') proc.bind(None, _mssql.SQLCHAR, '@ochar', output=True, max_length=4) proc.execute() self.assertEqual(input, proc.parameters['@ochar']) def testText(self): input = 'test' proc = self.mssql.init_procedure('pymssqlTestText') proc.bind(input, _mssql.SQLTEXT, '@itext') proc.bind(None, _mssql.SQLVARCHAR, '@otext', output=True) proc.execute() self.assertEqual(input, proc.parameters['@otext']) def testVarChar(self): input = 'test' proc = self.mssql.init_procedure('pymssqlTestVarChar') proc.bind(input, _mssql.SQLVARCHAR, '@ivarchar') proc.bind(None, _mssql.SQLVARCHAR, '@ovarchar', output=True) proc.execute() self.assertEqual(input, proc.parameters['@ovarchar']) def testVarBinary(self): def check_conversion(input, output_type): proc = self.mssql.init_procedure('pymssqlTestVarBinary') proc.bind(input, _mssql.SQLVARBINARY, '@ivarbinary') proc.bind(None, _mssql.SQLVARBINARY, '@ovarbinary', output=True) proc.execute() self.assertEqual(input, proc.parameters['@ovarbinary']) self.assertEqual(output_type, type(proc.parameters['@ovarbinary'])) if sys.version_info[0] == 3: check_conversion(bytes(b'\xDE\xAD\xBE\xEF'), bytes) check_conversion(bytearray(b'\xDE\xAD\xBE\xEF'), bytes) with pytest.raises(TypeError) as exc_info: check_conversion('FOO', bytes) assert 'value can only be bytes or bytearray' == str(exc_info.value) else: check_conversion(b'\xDE\xAD\xBE\xEF', str) check_conversion(bytes(b'\xDE\xAD\xBE\xEF'), str) check_conversion(bytearray(b'\xDE\xAD\xBE\xEF'), str) with pytest.raises(TypeError) as exc_info: check_conversion(unicode('Foo'), str) assert 'value can only be str or bytearray' == str(exc_info.value) @pytest.mark.mssql_server_required class TestFloatTypeConversion(unittest.TestCase): def setUp(self): self.mssql = mssqlconn() self.pymssql = pymssqlconn() cursor = self.pymssql.cursor() self.mssql.execute_non_query(""" CREATE PROCEDURE [dbo].[pymssqlRealTest] @inparam real AS BEGIN SELECT @inparam AS outparam END """ ) self.mssql.execute_non_query(""" CREATE PROCEDURE [dbo].[pymssqlFloatTest] @inparam float AS BEGIN SELECT @inparam AS outparam END """ ) def tearDown(self): cursor = self.pymssql.cursor() self.mssql.execute_non_query('DROP PROCEDURE [dbo].[pymssqlRealTest]') self.mssql.execute_non_query('DROP PROCEDURE [dbo].[pymssqlFloatTest]') self.pymssql.close() def testReal(self): cursor = self.pymssql.cursor() cursor.callproc( 'pymssqlRealTest', (0.5,)) # TODO: Use the solution we implement once #134 gets fixed a = next(cursor) assert abs(a[0] - 0.5) < 0.000001 def testFloat8(self): cursor = self.pymssql.cursor() cursor.callproc( 'pymssqlFloatTest', (5.44451787074e+39,)) # TODO: Use the solution we implement once #134 gets fixed a = next(cursor) assert abs(a[0] - 5.44451787074e+39) < 0.000001 @pytest.mark.mssql_server_required class TestErrorInSP(unittest.TestCase): def setUp(self): self.pymssql = pymssqlconn() cursor = self.pymssql.cursor() sql = u""" CREATE PROCEDURE [dbo].[SPThatRaisesAnError] AS BEGIN -- RAISERROR -- Generates an error message and initiates error processing for the session. -- http://msdn.microsoft.com/en-us/library/ms178592.aspx -- Severity levels from 0 through 18 can be specified by any user. RAISERROR('Error message', 18, 1) RETURN END """ cursor.execute(sql) def tearDown(self): cursor = self.pymssql.cursor() cursor.execute('DROP PROCEDURE [dbo].[SPThatRaisesAnError]') self.pymssql.close() def test_tsql_to_python_exception_translation(self): """An error raised by a SP is translated to a PEP-249-dictated, pymssql layer exception.""" # See https://github.com/pymssql/pymssql/issues/61 cursor = self.pymssql.cursor() # Must raise an exception self.assertRaises(Exception, cursor.callproc, 'SPThatRaisesAnError') # Must be a PEP-249 exception, not a _mssql-layer one try: cursor.callproc('SPThatRaisesAnError') except Exception as e: self.assertTrue(isinstance(e, pymssql.Error)) # Must be a DatabaseError exception try: cursor.callproc('SPThatRaisesAnError') except Exception as e: self.assertTrue(isinstance(e, pymssql.DatabaseError)) @pytest.mark.mssql_server_required class TestSPWithQueryResult(unittest.TestCase): SP_NAME = 'SPWithAQuery' def setUp(self): self.mssql = mssqlconn() self.pymssql = pymssqlconn() self.mssql.execute_non_query(""" CREATE PROCEDURE [dbo].[%(spname)s] @some_arg NVARCHAR(64) AS BEGIN SELECT @some_arg + N'!', @some_arg + N'!!' END """ % {'spname': self.SP_NAME}) def tearDown(self): self.mssql.execute_non_query('DROP PROCEDURE [dbo].[%(spname)s]' % {'spname': self.SP_NAME}) self.pymssql.close() self.mssql.close() def testPymssql(self): cursor = self.pymssql.cursor() cursor.callproc( self.SP_NAME, ('hello',)) a, b = cursor.fetchone() self.assertEqual(a, 'hello!') self.assertEqual(b, 'hello!!') def test_mssql(self): proc = self.mssql.init_procedure(self.SP_NAME) proc.bind('hello', _mssql.SQLVARCHAR) proc.execute() for row_dict in self.mssql: self.assertEqual(row_dict, {0: 'hello!', 1: 'hello!!'}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_sqlalchemy.py0000644000000000000000000000352700000000000020170 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Basic SQLAlchemy tests. """ import unittest import pytest from .helpers import config, eq_ try: import sqlalchemy as sa except ImportError: pytest.skip('SQLAlchemy is not available', allow_module_level=True) from sqlalchemy.orm import sessionmaker, declarative_base engine = sa.create_engine( 'mssql+pymssql://%s:%s@%s:%s/%s' % ( config.user, config.password, config.server, config.port, config.database ), echo=False ) meta = sa.MetaData() Base = declarative_base(metadata=meta) Session = sessionmaker(bind=engine) #sess = Session() class SAObj(Base): __tablename__ = 'sa_test_objs' id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String(50)) data = sa.Column(sa.PickleType) #saotbl = SAObj.__table__ # #saotbl.drop(engine, checkfirst=True) #saotbl.create(engine) @pytest.mark.mssql_server_required class TestSA(unittest.TestCase): def setUp(self): self.sess = Session() self.saotbl = SAObj.__table__ self.saotbl.drop(engine, checkfirst=True) self.saotbl.create(engine) def tearDown(self): # issue rollback first, otherwise clearing the table might give us # an error that the session is in a bad state self.sess.rollback() self.sess.execute(SAObj.__table__.delete()) self.sess.commit() def test_basic_usage(self): s = SAObj(name='foobar') self.sess.add(s) self.sess.commit() assert s.id assert self.sess.query(SAObj).count() == 1 def test_pickle_type(self): s = SAObj(name='foobar', data=['one']) self.sess.add(s) self.sess.commit() res = self.sess.execute(sa.select(self.saotbl.c.data)) row = res.fetchone() eq_(row[0], ['one']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_threaded.py0000644000000000000000000000627400000000000017610 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test usage in threads. """ import sys import threading import time import unittest import pytest from pymssql._mssql import MSSQLDatabaseException from .helpers import mssqlconn, StoredProc error_sproc = StoredProc( "pymssqlErrorThreadTest", args=(), body="SELECT unknown_column FROM unknown_table") class _TestingThread(threading.Thread): def __init__(self): super(_TestingThread, self).__init__() self.results = [] self.exc = None def run(self): try: with mssqlconn() as mssql: for i in range(0, 1000): num = mssql.execute_scalar('SELECT %d', (i,)) assert num == i self.results.append(num) except Exception as exc: self.exc = exc @pytest.mark.mssql_server_required class _TestingErrorThread(_TestingThread): def run(self): try: with mssqlconn() as mssql: mssql.execute_query('SELECT unknown_column') except Exception as exc: self.exc = exc @pytest.mark.mssql_server_required class _SprocTestingErrorThread(_TestingThread): def run(self): try: with mssqlconn() as mssql: error_sproc.execute(mssql=mssql) except Exception as exc: self.exc = exc @pytest.mark.mssql_server_required class ThreadedTests(unittest.TestCase): def run_threads(self, num, thread_class): threads = [thread_class() for _ in range(num)] for thread in threads: thread.start() results = [] exceptions = [] while len(threads) > 0: sys.stdout.write(".") sys.stdout.flush() for thread in threads: if not thread.is_alive(): threads.remove(thread) if thread.results: results.append(thread.results) if thread.exc: exceptions.append(thread.exc) time.sleep(5) sys.stdout.write(" ") sys.stdout.flush() return results, exceptions @pytest.mark.slow def testThreadedUse(self): results, exceptions = self.run_threads( num=50, thread_class=_TestingThread) self.assertEqual(len(exceptions), 0) for result in results: self.assertEqual(result, list(range(0, 1000))) @pytest.mark.slow def testErrorThreadedUse(self): results, exceptions = self.run_threads( num=2, thread_class=_TestingErrorThread) self.assertEqual(len(exceptions), 2) for exc in exceptions: self.assertEqual(type(exc), MSSQLDatabaseException) @pytest.mark.slow def testErrorSprocThreadedUse(self): with error_sproc.create(): results, exceptions = self.run_threads( num=5, thread_class=_SprocTestingErrorThread) self.assertEqual(len(exceptions), 5) for exc in exceptions: self.assertEqual(type(exc), MSSQLDatabaseException) suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ThreadedTests)) if __name__ == '__main__': unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_types.py0000644000000000000000000002761500000000000017176 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- import binascii from datetime import time from datetime import date from datetime import datetime import decimal from decimal import Decimal as D from hashlib import md5 import pickle import sys import unittest import uuid import pytest import pymssql from .helpers import get_sql_server_version def get_bytes_buffer(): from io import BytesIO return BytesIO() from .helpers import drop_table, mssqlconn, clear_table, config, eq_, pymssqlconn def typeeq(v1, v2): eq_(type(v1), type(v2)) create_test_table_sql = [ 'CREATE TABLE pymssql (' ' pk_id int IDENTITY (1, 1) NOT NULL,', ' real_no real,', ' float_no float,', ' money_no money,', ' stamp_datetime datetime,', ' data_bit bit,', ' comment_vch varchar(50),', ' comment_nvch nvarchar(50),', ' comment_text text,', ' comment_ntext ntext,', ' data_image image,', ' data_binary varbinary(40),', ' decimal_no decimal(38,2),', ' decimal_no2 decimal(38,10),', ' numeric_no numeric(38,8),', ' stamp_timestamp timestamp,', ' uuid uniqueidentifier', ')', ] def build_create_table_query(conn): if get_sql_server_version(conn) < 2008: create_sql = '\n'.join(create_test_table_sql) else: create_sql = '\n'.join( create_test_table_sql[:-1] + [ ',' 'stamp_date date,', 'stamp_time time,', 'stamp_datetime2 datetime2'] + create_test_table_sql[-1:]) return create_sql @pytest.mark.mssql_server_required class TestTypes(unittest.TestCase): tname = 'pymssql' @classmethod def setup_class(cls): cls.conn = mssqlconn() drop_table(cls.conn, cls.tname) ddl_str = build_create_table_query(cls.conn) cls.conn.execute_non_query(ddl_str) def setUp(self): clear_table(self.conn, self.tname) def hasheq(self, v1, v2): if sys.version_info >= (3, ): if hasattr(v1, 'encode'): v1 = v1.encode('utf-8') if hasattr(v2, 'encode'): v2 = v2.encode('utf-8') hd1 = md5(v1).hexdigest() hd2 = md5(v2).hexdigest() assert hd1 == hd2, '%s (%s) != %s (%s)' % (v1, hd1, v2, hd2) def insert_and_select(self, cname, value, vartype, params_as_dict=False): if params_as_dict: inssql = 'insert into %s (%s) values (%%(value)%s)' % (self.tname, cname, vartype) self.conn.execute_non_query(inssql, dict(value=value)) else: inssql = 'insert into %s (%s) values (%%%s)' % (self.tname, cname, vartype) self.conn.execute_non_query(inssql, value) self.conn.execute_query('select %s from pymssql' % cname) rows = tuple(self.conn) eq_(len(rows), 1) cval = rows[0][cname] return cval def test_varchar(self): testval = 'foobar' colval = self.insert_and_select('comment_vch', testval, 's') typeeq('foobar', colval) self.hasheq('foobar', colval) def test_varchar_hex(self): testval = '0xf00' colval = self.insert_and_select('comment_vch', testval, 's') typeeq('0xf00', colval) self.hasheq('0xf00', colval) def test_varchar_unicode(self): testval = 'foobär' colval = self.insert_and_select('comment_vch', testval, 's') typeeq('foobär', colval) eq_('foobär', colval) def test_nvarchar_unicode(self): testval = 'foobär' colval = self.insert_and_select('comment_nvch', testval, 's') typeeq(testval, colval) eq_(testval, colval) def test_binary_bytearray(self): bindata = '{z\n\x03\x07\x194;\x034lE4ISo'.encode('ascii') colval = self.insert_and_select('data_binary', bytearray(bindata), 's') typeeq(bindata, colval) eq_(bindata, colval) def test_binary_Binary(self): """ See https://github.com/pymssql/pymssql/issues/504 """ bindata = '{z\n\x03\x07\x194;\x034lE4ISo'.encode('ascii') colval = self.insert_and_select('data_binary', pymssql.Binary(bindata), 's') typeeq(bindata, colval) eq_(bindata, colval) def test_image(self): buf = get_bytes_buffer() longstr = 'a'*4000 pickle.dump([1, 2, longstr], buf, -1) testval = buf.getvalue() colval = self.insert_and_select('data_image', testval, 's') typeeq(testval, colval) self.hasheq(testval, colval) tlist = pickle.loads(colval) eq_(tlist, [1, 2, longstr]) def test_image_gt_4KB(self): """ test_image_gt_4KB By default, SQL server sets TEXTSIZE = 4096 bytes. We up that by default and want to make sure it applies. """ buf = get_bytes_buffer() longstr = 'a'*5000 pickle.dump([1, 2, longstr], buf, -1) testval = buf.getvalue() colval = self.insert_and_select('data_image', testval, 's') typeeq(testval, colval) self.hasheq(testval, colval) tlist = pickle.loads(colval) eq_(tlist, [1, 2, longstr]) def test_datetime(self): # Test for issue at https://code.google.com/p/pymssql/issues/detail?id=118 testval = datetime(2013, 1, 2, 3, 4, 5, 3000) colval = self.insert_and_select('stamp_datetime', testval, 's') typeeq(testval, colval) eq_(testval, colval) def test_datetime_params_as_dict(self): testval = datetime(2013, 1, 2, 3, 4, 5, 3000) colval = self.insert_and_select('stamp_datetime', testval, 's', params_as_dict=True) typeeq(testval, colval) eq_(testval, colval) def test_date(self): if get_sql_server_version(self.conn) < 2008: pytest.skip("DATE field type isn't supported by SQL Server versions prior to 2008.") if self.conn.tds_version < 7.3: pytest.skip("DATE field type isn't supported by TDS protocol older than 7.3.") testval = date(2013, 1, 2) colval = self.insert_and_select('stamp_date', testval, 's') typeeq(testval, colval) eq_(testval, colval) def test_ancient_date(self): if get_sql_server_version(self.conn) < 2008: pytest.skip("DATE field type isn't supported by SQL Server versions prior to 2008.") if self.conn.tds_version < 7.3: pytest.skip("DATE field type isn't supported by TDS protocol older than 7.3.") testval = date(13, 1, 2) colval = self.insert_and_select('stamp_date', testval, 's') typeeq(testval, colval) eq_(testval, colval) def test_time(self): if get_sql_server_version(self.conn) < 2008: pytest.skip("TIME field type isn't supported by SQL Server versions prior to 2008.") if self.conn.tds_version < 7.3: pytest.skip("TIME field type isn't supported by TDS protocol older than 7.3.") testval = datetime(2013, 1, 2, 3, 4, 5, 3000) colval = self.insert_and_select('stamp_time', testval, 's') testval_no_date = testval.time() typeeq(testval_no_date, colval) eq_(testval_no_date, colval) def test_datetime2(self): if get_sql_server_version(self.conn) < 2008: pytest.skip("DATETIME2 field type isn't supported by SQL Server versions prior to 2008.") if self.conn.tds_version < 7.3: pytest.skip("DATETIME2 field type isn't supported by TDS protocol older than 7.3.") testval = datetime(2013, 1, 2, 3, 4, 5, 3000) colval = self.insert_and_select('stamp_datetime2', testval, 's') typeeq(testval, colval) eq_(testval, colval) def test_decimal(self): # test rounding down origval = D('1.2345') expect = D('1.23') colval = self.insert_and_select('decimal_no', origval, 's') typeeq(expect, colval) eq_(expect, colval) def test_decimal_context_protection(self): origval = D('1.2345') colval = self.insert_and_select('decimal_no', origval, 's') # make sure our manipulation of the decimal values doesn't affect the # default decimal context eq_(decimal.getcontext().prec, config.orig_decimal_prec) def test_decimal_rounding_up(self): # test rounding up origval = D('1.235') expect = D('1.24') colval = self.insert_and_select('decimal_no', origval, 's') typeeq(expect, colval) eq_(expect, colval) def test_decimal_smaller_precision(self): # smaller precision than column origval = D('1.2345') expect = D('1.2345000000') colval = self.insert_and_select('decimal_no2', origval, 's') typeeq(expect, colval) eq_(expect, colval) def test_numeric(self): # should be handled the same as a decimal column, so only one test origval = D('1.2345') expect = D('1.23450000') colval = self.insert_and_select('numeric_no', origval, 's') typeeq(expect, colval) eq_(expect, colval) def test_float_precision(self): #origval and expect are not exactly the same, but they test # equal and that is what we are getting at. They should have # the same value out to the 16th digit. origval = 1.23456789012345670 expect = 1.23456789012345671 colval = self.insert_and_select('float_no', origval, 's') typeeq(expect, colval) eq_(expect, colval) def test_uuid_passed_as_string(self): """ Test the case when the application passes a string representation of the uuid.UUID data type to _mssql. """ origval = uuid.uuid4() stestval = str(origval) colval = self.insert_and_select('uuid', stestval, 's') typeeq(origval, colval) eq_(origval, colval) def test_uuid_passed_as_python_datatype(self): """ Test the case when the application passes an instance of the uuid.UUID data type to _mssql to confirm it can handle it. """ origval = uuid.uuid4() colval = self.insert_and_select('uuid', origval, 's') typeeq(origval, colval) eq_(origval, colval) @pytest.mark.mssql_server_required class TestTypesPymssql(unittest.TestCase): tname = 'pymssql' @classmethod def setup_class(cls): cls.conn = pymssqlconn() drop_table(cls.conn._conn, cls.tname) ddl_str = build_create_table_query(cls.conn._conn) with cls.conn.cursor() as c: c.execute(ddl_str) def setUp(self): clear_table(self.conn._conn, self.tname) def insert_and_select(self, cname, value, vartype): with self.conn.cursor() as c: inssql = 'insert into %s (%s) values (%%%s)' % (self.tname, cname, vartype) c.execute(inssql, value) c.execute('select %s from %s' % (cname, self.tname)) rows = c.fetchall() eq_(len(rows), 1) cval = rows[0][0] return cval def test_uuid_passed_as_string(self): """ The uuid.UUID type isn't supported by the pyformat paramstyle (that only supports '%s' and is the style supported by pymsql). Test the case when the application passes a string representation of such data type to _mssql. """ origval = uuid.uuid4() stestval = str(origval) colval = self.insert_and_select('uuid', stestval, 's') typeeq(origval, colval) eq_(origval, colval) def test_uuid_passed_as_python_datatype(self): """ The uuid.UUID type isn't supported by the pyformat paramstyle (that only supports '%s' and is the style supported by pymsql). Test the case when the application passes an instance of such data type to _mssql to confirm it can handle it. """ origval = uuid.uuid4() colval = self.insert_and_select('uuid', origval, 's') typeeq(origval, colval) eq_(origval, colval) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_unicode.py0000644000000000000000000000136400000000000017451 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test unicode usage in queries. """ import unittest import pytest from .helpers import pymssqlconn @pytest.mark.mssql_server_required class TestUnicode(unittest.TestCase): def setUp(self): self.conn = pymssqlconn() def test_unicode(self): # This test is for http://code.google.com/p/pymssql/issues/detail?id=60 # Thanks to tonal.promsoft for reporting the issue and submitting a # patch. cursor = self.conn.cursor() cursor.execute( 'select %s, %s', ('Здравствуй', 'Мир')) # Russian strings a, b = cursor.fetchone() self.assertEqual(a, 'Здравствуй') self.assertEqual(b, 'Мир') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_user_msghandler.py0000644000000000000000000001213700000000000021205 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test user message hanler. """ import unittest import pytest from .helpers import config, mssqlconn msgs = [] def user_msg_handler1(msgstate, severity, srvname, procname, line, msgtext): global msgs procname = procname.decode('ascii') msgtext = msgtext.decode('ascii') entry = (u"msg_handler1: msgstate = %d, severity = %d, procname = '%s', " "line = %d, msgtext = '%s'") % (msgstate, severity, procname, line, msgtext) msgs.append(entry) def user_msg_handler2(msgstate, severity, srvname, procname, line, msgtext): global msgs procname = procname.decode('ascii') msgtext = msgtext.decode('ascii') entry = ("msg_handler2: msgstate = %d, severity = %d, procname = '%s', " "line = %d, msgtext = '%s'") % (msgstate, severity, procname, line, msgtext) msgs.append(entry) def wrong_signature_msg_handler(): pass @pytest.mark.mssql_server_required class TestUserMsgHandler(unittest.TestCase): def test_basic_functionality(self): cnx = mssqlconn() try: cnx.set_msghandler(user_msg_handler1) msgs_before = len(msgs) cnx.execute_non_query("USE master") msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 1) expect = ("msg_handler1: msgstate = 1, severity = 0, procname = ''" ", line = 1, msgtext = 'Changed database context to 'master'.'") self.assertEqual(expect, msgs[msgs_after - 1]) finally: cnx.close() def test_set_handler_to_none(self): cnx = mssqlconn() try: cnx.set_msghandler(None) msgs_before = len(msgs) cnx.execute_non_query("USE master") msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 0) finally: cnx.close() def test_change_handler(self): cnx = mssqlconn() try: cnx.set_msghandler(user_msg_handler1) msgs_before = len(msgs) cnx.execute_non_query("USE master") msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 1) expect = ("msg_handler1: msgstate = 1, severity = 0, procname = ''" ", line = 1, msgtext = 'Changed database context to 'master'.'") self.assertEqual(expect, msgs[msgs_after - 1]) cnx.set_msghandler(user_msg_handler2) msgs_before = len(msgs) cnx.execute_non_query("USE %s" % config.database) msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 1) expect = ("msg_handler2: msgstate = 1, severity = 0, procname = ''" ", line = 1, msgtext = 'Changed database context to '%s'.'") % config.database self.assertEqual(expect, msgs[msgs_after - 1]) finally: cnx.close() def test_per_conn_handlers(self): cnx1 = mssqlconn() cnx2 = mssqlconn() try: cnx1.set_msghandler(user_msg_handler1) msgs_before = len(msgs) cnx1.execute_non_query("USE master") msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 1) expect = ("msg_handler1: msgstate = 1, severity = 0, procname = ''" ", line = 1, msgtext = 'Changed database context to 'master'.'") self.assertEqual(expect, msgs[msgs_after - 1]) cnx2.set_msghandler(user_msg_handler2) msgs_before = len(msgs) cnx2.execute_non_query("USE %s" % config.database) msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 1) expect = ("msg_handler2: msgstate = 1, severity = 0, procname = ''" ", line = 1, msgtext = 'Changed database context to '%s'.'") % config.database self.assertEqual(expect, msgs[msgs_after - 1]) finally: cnx1.close() cnx2.close() @staticmethod def user_msg_handler3(msgstate, severity, srvname, procname, line, msgtext): global msgs procname = procname.decode('ascii') msgtext = msgtext.decode('ascii') entry = ("msg_handler3 called") msgs.append(entry) def test_static_method_handler(self): cnx = mssqlconn() try: cnx.set_msghandler(self.user_msg_handler3) msgs_before = len(msgs) cnx.execute_non_query("USE master") msgs_after = len(msgs) delta = msgs_after - msgs_before self.assertEqual(delta, 1) expect = ("msg_handler3 called") self.assertEqual(expect, msgs[msgs_after - 1]) finally: cnx.close() def test_wrong_signature_handler(self): cnx = mssqlconn() try: cnx.set_msghandler(wrong_signature_msg_handler) cnx.execute_non_query("USE master") finally: cnx.close() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/test_utils.py0000644000000000000000000001374700000000000017173 0ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test parameters substitution. """ from .helpers import eq_ from pymssql._mssql import substitute_params def test_single_param(): res = substitute_params( 'SELECT * FROM employees WHERE id = %s', 13) eq_(res, b'SELECT * FROM employees WHERE id = 13') res = substitute_params( 'SELECT * FROM empl WHERE name = %s', b'John Doe') eq_(res, b"SELECT * FROM empl WHERE name = 'John Doe'") res = substitute_params( 'SELECT * FROM empl WHERE name = %s', 'John Doe') eq_(res, b"SELECT * FROM empl WHERE name = N'John Doe'") def test_param_quote(): res = substitute_params( 'SELECT * FROM empl WHERE name = %s', b"John's Doe") eq_(res, b"SELECT * FROM empl WHERE name = 'John''s Doe'") res = substitute_params( 'SELECT * FROM empl WHERE name = %s', "John's Doe") eq_(res, b"SELECT * FROM empl WHERE name = N'John''s Doe'") def test_unicode_params(): res = substitute_params( 'SELECT * FROM \u0394 WHERE name = %s', '\u03A8' ) eq_(res, b"SELECT * FROM \xce\x94 WHERE name = N'\xce\xa8'") res = substitute_params(u"testing ascii (\u0105\u010D\u0119) 1=%d 'one'=%s", (1, 'str')) eq_(res, b"testing ascii (\xc4\x85\xc4\x8d\xc4\x99) 1=1 'one'=N'str'") def test_single_param_with_d(): res = substitute_params( 'SELECT * FROM employees WHERE id = %d', 13) eq_(res, b'SELECT * FROM employees WHERE id = 13') def test_keyed_param_with_d(): res = substitute_params( 'SELECT * FROM employees WHERE id = %(emp_id)d', {'emp_id': 13}) eq_(res, b'SELECT * FROM employees WHERE id = 13') def test_percent_not_touched_with_no_params(): sql = "SELECT COUNT(*) FROM employees WHERE name LIKE 'J%'" res = substitute_params(sql, None) eq_(res, sql) def test_tuple_with_in(): res = substitute_params( 'SELECT * FROM empl WHERE id IN %s', ((5, 6),)) eq_(res, b"SELECT * FROM empl WHERE id IN (5,6)") res = substitute_params( 'SELECT * FROM empl WHERE id IN %s', ((b'foo', b'bar'),)) eq_(res, b"SELECT * FROM empl WHERE id IN ('foo','bar')") res = substitute_params( 'SELECT * FROM empl WHERE id IN %s', (('foo', 'bar'),)) eq_(res, b"SELECT * FROM empl WHERE id IN (N'foo',N'bar')") # single item res = substitute_params( 'SELECT * FROM empl WHERE id IN %s', ((b'foo',),)) eq_(res, b"SELECT * FROM empl WHERE id IN ('foo')") res = substitute_params( 'SELECT * FROM empl WHERE id IN %s', (('foo',),)) eq_(res, b"SELECT * FROM empl WHERE id IN (N'foo')") def test_percent_in_param(): res = substitute_params( 'SELECT * FROM empl WHERE name LIKE %s', b'J%') eq_(res, b"SELECT * FROM empl WHERE name LIKE 'J%'") res = substitute_params( 'SELECT * FROM empl WHERE name LIKE %s', 'J%') eq_(res, b"SELECT * FROM empl WHERE name LIKE N'J%'") def test_single_dict_params(): res = substitute_params( 'SELECT * FROM cust WHERE salesrep = %(name)s', {'name': b'John Doe'}) eq_(res, b"SELECT * FROM cust WHERE salesrep = 'John Doe'") res = substitute_params( 'SELECT * FROM cust WHERE salesrep = %(name)s', {'name': 'John Doe'}) eq_(res, b"SELECT * FROM cust WHERE salesrep = N'John Doe'") def test_weird_key_names_dict_params(): res = substitute_params( 'SELECT * FROM cust WHERE salesrep = %(n %s ##ame)s', {'n %s ##ame': b'John Doe'}) eq_(res, b"SELECT * FROM cust WHERE salesrep = 'John Doe'") res = substitute_params( 'SELECT * FROM cust WHERE salesrep = %(n %s ##ame)s', {'n %s ##ame': 'John Doe'}) eq_(res, b"SELECT * FROM cust WHERE salesrep = N'John Doe'") def test_multi_dict_params(): res = substitute_params( 'SELECT * FROM empl ' 'WHERE (name = %(name)s AND city = %(city)s) ' 'OR supervisor = %(name)s', {'name': b'John Doe', 'city': b'Nowhere'}) eq_(res, b"SELECT * FROM empl " b"WHERE (name = 'John Doe' AND city = 'Nowhere') " b"OR supervisor = 'John Doe'") res = substitute_params( 'SELECT * FROM empl ' 'WHERE (name = %(name)s AND city = %(city)s) ' 'OR supervisor = %(name)s', {'name': 'John Doe', 'city': 'Nowhere'}) eq_(res, b"SELECT * FROM empl " b"WHERE (name = N'John Doe' AND city = N'Nowhere') " b"OR supervisor = N'John Doe'") def test_single_and_tuple(): res = substitute_params( 'SELECT * FROM cust ' 'WHERE salesrep = %s AND id IN %s', (b'John Doe', (1, 2, 3))) eq_(res, b"SELECT * FROM cust " b"WHERE salesrep = 'John Doe' AND id IN (1,2,3)") res = substitute_params( 'SELECT * FROM cust ' 'WHERE salesrep = %s AND id IN %s', ('John Doe', (1, 2, 3))) eq_(res, b"SELECT * FROM cust " b"WHERE salesrep = N'John Doe' AND id IN (1,2,3)") def test_bare_percent_position(): res = substitute_params('select 5 % %s', 3) eq_(res, b"select 5 % 3") def test_bare_percent_dict(): res = substitute_params('select 5 % %(divisor)s', {'divisor': 3}) eq_(res, b"select 5 % 3") def test_missing_dict_param(): expected_err = 'params dictionary did not contain value for placeholder' try: substitute_params( 'SELECT * FROM cust WHERE salesrep = %(name)s', {'foobar': 'John Doe'}) assert False, \ 'expected exception b/c dict did not contain replacement value' except ValueError as exc: if expected_err not in str(exc): raise def test_too_many_params(): try: substitute_params( 'SELECT * FROM cust WHERE salesrep = %s and foo = %s', ('bar',)) assert False, 'expected exception b/c too many params in sql' except ValueError as exc: if 'more placeholders in sql than params available' not in str(exc): raise ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tests/tests.cfg.tpl0000644000000000000000000000124700000000000017033 0ustar00rootroot00000000000000# in order for the tests to work correctly, the value for server # SHOULD NOT appear in any freetds.conf file. # # Ideally, the test DB server would be a non-default instance which allows full # tests of the instance and port parameters. [DEFAULT] server = sqlserver username = sa password = YourStrong!Passw0rd database = tempdb port = 1433 ipaddress = 10.5.0.5 # Instance isn't working with docker even though select @@servicename returns MSSQLSERVER # instance = MSSQLSERVER # this shows all options need to run all tests [AllTestsWillRun] server = mydbserver ipaddress = 192.168.1.1 username = foouser password = somepass database = testdb port = 1435 instance = testinst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1701662178.0 pymssql-2.2.11/tox.ini0000644000000000000000000000115100000000000014555 0ustar00rootroot00000000000000[tox] envlist = py27, py34, py35, py36 [testenv] changedir = {toxworkdir} commands = python -c 'import pymssql; print(pymssql); print("pymssql.__version__ = %r" % pymssql.__version__)' python -c 'from pymssql import _mssql; print(_mssql); print("_mssql.__version__ = %r" % _mssql.__version__)' py.test {posargs:-v {toxinidir}/tests/} deps = Cython ipdb pytest SQLAlchemy [testenv:py27] deps = {[testenv]deps} gevent [testenv:docs] deps = Sphinx sphinx_rtd_theme changedir = docs commands = {envbindir}/sphinx-build -W -b html -d {envtmpdir}/doctrees . ./_build/html