pax_global_header00006660000000000000000000000064135772041170014521gustar00rootroot0000000000000052 comment=3469e6d4300e58eb19a9398094dcd66864c3505a vertica-python-0.10.1/000077500000000000000000000000001357720411700145545ustar00rootroot00000000000000vertica-python-0.10.1/.gitignore000066400000000000000000000007441357720411700165510ustar00rootroot00000000000000*.py[cod] test.py # Test configuration vp_test.conf # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .DS_Store *.iml # vagrant .vagrant # pycharm .idea # default virtual environment /env/ # pyenv .python-version # backup files end in ~ *~vertica-python-0.10.1/.travis.yml000066400000000000000000000014151357720411700166660ustar00rootroot00000000000000dist: xenial language: python services: - docker matrix: include: - python: 3.8 env: TOXENV=py38 - python: 3.7 env: TOXENV=py37 - python: 3.6 env: TOXENV=py36 - python: 3.5 env: TOXENV=py35 - python: 3.4 env: TOXENV=py34 - python: 2.7 env: TOXENV=py27 before_install: - git clone https://github.com/jbfavre/docker-vertica.git - curl $VERTICA_CE_URL --create-dirs -o docker-vertica/packages/vertica-ce.latest.rpm - docker build -f docker-vertica/Dockerfile.centos.7_9.x --build-arg VERTICA_PACKAGE=vertica-ce.latest.rpm -t jbfavre/vertica docker-vertica - docker images - docker run -d -p 5433:5433 jbfavre/vertica - sleep 60 install: - pip install tox script: - export VP_TEST_USER=dbadmin - tox vertica-python-0.10.1/AUTHORS000066400000000000000000000047731357720411700156370ustar00rootroot00000000000000vertica-python was originally created in 2013 at Uber Technologies, Inc. This is a non-exhaustive list of vertica-python contributors. This does not necessarily list everyone who has contributed code. To see the full list of contributors, see the revision history in source control Aditya Sarawgi Aiden Scandella Aiden Scandella Alex Kim Alex Kim Alvin Wu asamasoma Ben Feinstein Berton Earnshaw Brad Hurley Carl Ekerot Conrad Whelan Deepak Prakash Dennis O'Brien Eric Sayle esmioley Flo Fredrik Appelros Gabriel Haim Heli Wang Heli Wang Ian Zelikman Jakub Jedelsky James Brown James Brown jberka Jean Baptiste Favre Jeff Kim Johan Olofsson Josh Varner jsingh3 Justin Berka Justin Berka Kenneth Tran kennethdamica Kevin Sweeney Konstantin lemeryfertitta Lubo Lubo Slivka luke Mark R. nchammas Nicholas Travers Paulius Šileikis Peder Andersen pedercandersen Peter M. Landwehr Praveen Jain Rahul Agarwal rforgione Richard Tom Ruwen Jin Sevag Hanssian Shiva M Steven van Stiphout Thom Neale Thom Neale Timothy Heys tudit tumuluru twneale Udit Tumuluri zer0n vertica-python-0.10.1/CONTRIBUTING.md000066400000000000000000000260241357720411700170110ustar00rootroot00000000000000First off, thank you for considering contributing to *vertica-python* and helping make it even better than it is today! This document will guide you through the contribution process. There are a number of ways you can help: - [Bug Reports](#bug-reports) - [Feature Requests](#feature-requests) - [Code Contributions](#code-contributions) # Bug Reports If you find a bug, submit an [issue](https://github.com/vertica/vertica-python/issues) with a complete and reproducible bug report. If the issue can't be reproduced, it will be closed. If you opened an issue, but figured out the answer later on your own, comment on the issue to let people know, then close the issue. For issues (e.g. security related issues) that are **not suitable** to be reported publicly on the GitHub issue system, report your issues to [Vertica open source team](mailto:vertica-opensrc@microfocus.com) directly or file a case with Vertica support if you have a support account. # Feature Requests Feel free to share your ideas for how to improve *vertica-python*. We’re always open to suggestions. You can open an [issue](https://github.com/vertica/vertica-python/issues) with details describing what feature(s) you'd like added or changed. If you would like to implement the feature yourself, open an issue to ask before working on it. Once approved, please refer to the [Code Contributions](#code-contributions) section. # Code Contributions ## Step 1: Fork Fork the project [on Github](https://github.com/vertica/vertica-python) and check out your copy locally. ```shell git clone git@github.com:YOURUSERNAME/vertica-python.git cd vertica-python ``` Your GitHub repository **YOURUSERNAME/vertica-python** will be called "origin" in Git. You should also setup **vertica/vertica-python** as an "upstream" remote. ```shell git remote add upstream git@github.com:vertica/vertica-python.git git fetch upstream ``` ### Configure Git for the first time Make sure git knows your [name](https://help.github.com/articles/setting-your-username-in-git/ "Set commit username in Git") and [email address](https://help.github.com/articles/setting-your-commit-email-address-in-git/ "Set commit email address in Git"): ```shell git config --global user.name "John Smith" git config --global user.email "email@example.com" ``` ## Step 2: Branch Create a new branch for the work with a descriptive name: ```shell git checkout -b my-fix-branch ``` ## Step 3: Install dependencies Install the Python dependencies for development: ```shell pip install -r requirements-dev.txt ``` If you do Kerberos development, you need to install additional [dependencies](README.md#using-kerberos-authentication). ## Step 4: Get the test suite running *vertica-python* comes with a test suite of its own, in the `vertica_python/tests` directory of the code base. It’s our policy to make sure all tests pass at all times. We appreciate any and all [contributions to the test suite](#tests)! These tests use a Python module: [pytest](https://docs.pytest.org/en/latest/). You might want to check out the pytest documentation for more details. There are two types of tests: unit tests and integration tests. Unit tests do simple unit testing of individual classes and functions, which do not require database connection. Integration tests need to connect to a Vertica database to run stuffs, so you must have access to a Vertica database. We recommend using a non-production database, because some tests need the superuser permission to manipulate global settings and potentially break that database. Heres one way to go about it: - Download docker kitematic: https://kitematic.com/ - Spin up a vertica container (e.g. sumitchawla/vertica) Spin up your Vertica database for integration tests and then config test settings: * Here are default settings: ```sh host: 'localhost' port: 5433 user: database: password: '' log_dir: 'vp_test_log' # all test logs would write to files under this directory log_level: logging.WARNING ``` * Override with a configuration file called `vertica_python/tests/common/vp_test.conf`. This is a file that would be ignored by git. We created an example `vertica_python/tests/common/vp_test.conf.example` for your reference. ```sh # edit under [vp_test_config] section VP_TEST_HOST=10.0.0.2 VP_TEST_PORT=5000 VP_TEST_USER=dbadmin VP_TEST_DATABASE=vdb1 VP_TEST_PASSWORD=abcdef1234 VP_TEST_LOG_DIR=my_log/year/month/date VP_TEST_LOG_LEVEL=DEBUG ``` * Override again with VP_TEST_* environment variables ```shell # Set environment variables in linux $ export VP_TEST_HOST=10.0.0.2 $ export VP_TEST_PORT=5000 $ export VP_TEST_USER=dbadmin $ export VP_TEST_DATABASE=vdb1 $ export VP_TEST_PASSWORD=abcdef1234 $ export VP_TEST_LOG_DIR=my_log/year/month/date $ export VP_TEST_LOG_LEVEL=DEBUG # Delete your environment variables after tests $ unset VP_TEST_PASSWORD ``` Tox (https://tox.readthedocs.io) is a tool for running those tests in different Python environments. *vertica-python* includes a `tox.ini` file that lists all Python versions we test. Tox is installed with the `requirements-dev.txt`, discussed above. Edit `tox.ini` envlist property to list the version(s) of Python you have installed. Then you can run the **tox** command from any place in the *vertica-python* source tree. If VP_TEST_LOG_DIR sets to a relative path, it will be in the *vertica-python* directory no matter where you run the **tox** command. Examples of running tests: ```bash # Run all tests using tox: tox # Run tests on specified python versions with `tox -e ENV,ENV` tox -e py27,py35 # Run specific tests by filename (e.g.) `test_notice.py` tox -- vertica_python/tests/unit_tests/test_notice.py # Run all unit tests on the python version 3.6: tox -e py36 -- -m unit_tests # Run all integration tests on the python version 3.4 with verbose result outputs: tox -e py34 -- -v -m integration_tests # Run an individual test on specified python versions. # e.g.: Run the test `test_error_message` under `test_notice.py` on the python versions 2.7 and 3.5 tox -e py27,py35 -- vertica_python/tests/unit_tests/test_notice.py::NoticeTestCase::test_error_message ``` The arguments after the `--` will be substituted everywhere where you specify `{posargs}` in your test *commands* of `tox.ini`, which are sent to pytest. See `pytest --help` to see all arguments you can specify after the `--`. You might also run `pytest` directly, which will evaluate tests in your current Python environment, rather than across the Python environments/versions that are enumerated in `tox.ini`. For more usages about [tox](https://tox.readthedocs.io), see the Python documentation. ## Step 5: Implement your fix or feature At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first. ### License Headers Every file in this project must use the following Apache 2.0 header (with the appropriate year or years in the "[yyyy]" box; if a copyright statement from another party is already present in the code, you may add the statement on top of the existing copyright statement): ``` Copyright (c) [yyyy] Micro Focus or one of its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ### Commits Make some changes on your branch, then stage and commit as often as necessary: ```shell git add . git commit -m 'Added two more tests for #166' ``` When writing the commit message, try to describe precisely what the commit does. The commit message should be in lines of 72 chars maximum. Include the issue number `#N`, if the commit is related to an issue. ### Tests Add appropriate tests for the bug’s or feature's behavior, run the test suite again and ensure that all tests pass. Here is the guideline for writing test: - Tests should be easy for any contributor to run. Contributors may not get complete access to their Vertica database, for example, they may only have a non-admin user with write privileges to a single schema, and the database may not be the latest version. We encourage tests to use only what they need and nothing more. - If there are requirements to the database for running a test, the test should adapt to different situations and never report a failure. For example, if a test depends on a multi-node database, it should check the number of DB nodes first, and skip itself when it connects to a single-node database (see helper function `require_DB_nodes_at_least()` in `vertica_python/tests/integration_tests/base.py`). ## Step 6: Push and Rebase You can publish your work on GitHub just by doing: ```shell git push origin my-fix-branch ``` When you go to your GitHub page, you will notice commits made on your local branch is pushed to the remote repository. When upstream (vertica/vertica-python) has changed, you should rebase your work. The **rebase** command creates a linear history by moving your local commits onto the tip of the upstream commits. You can rebase your branch locally and force-push to your GitHub repository by doing: ```shell git checkout my-fix-branch git fetch upstream git rebase upstream/master git push -f origin my-fix-branch ``` ## Step 7: Make a Pull Request When you think your work is ready to be pulled into *vertica-python*, you should create a pull request(PR) at GitHub. A good pull request means: - commits with one logical change in each - well-formed messages for each commit - documentation and tests, if needed Go to https://github.com/YOURUSERNAME/vertica-python and [make a Pull Request](https://help.github.com/articles/creating-a-pull-request/) to `vertica:master`. ### Sign the CLA Before we can accept a pull request, we first ask people to sign a Contributor License Agreement (or CLA). We ask this so that we know that contributors have the right to donate the code. You should notice a comment from **CLAassistant** on your pull request page, follow this comment to sign the CLA electronically. ### Review Pull requests are usually reviewed within a few days. If there are comments to address, apply your changes in new commits, rebase your branch and force-push to the same branch, re-run the test suite to ensure tests are still passing. We care about quality, Vertica has internal test suites to run as well, so your pull request won't be merged until all internal tests pass. In order to produce a clean commit history, our maintainers would do squash merging once your PR is approved, which means combining all commits of your PR into a single commit in the master branch. That's it! Thank you for your code contribution! After your pull request is merged, you can safely delete your branch and pull the changes from the upstream repository. vertica-python-0.10.1/LICENSE000066400000000000000000000261351357720411700155700ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. vertica-python-0.10.1/MANIFEST.in000066400000000000000000000002101357720411700163030ustar00rootroot00000000000000include LICENSE include requirements.txt include README.md recursive-include vertica_python *.py recursive-exclude vertica_python *.pyc vertica-python-0.10.1/README.md000066400000000000000000000451101357720411700160340ustar00rootroot00000000000000# vertica-python [![PyPI version](https://badge.fury.io/py/vertica-python.svg)](https://badge.fury.io/py/vertica-python) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg)](https://opensource.org/licenses/Apache-2.0) [![Python Version](https://img.shields.io/pypi/pyversions/vertica-python.svg)](https://www.python.org/downloads/) [![Downloads](https://pepy.tech/badge/vertica-python/week)](https://pepy.tech/project/vertica-python) vertica-python is a native Python adapter for the Vertica (http://www.vertica.com) database. :loudspeaker: 08/14/2018: *vertica-python* becomes Vertica’s first officially supported open source database client, see the blog [here](https://my.vertica.com/blog/vertica-python-becomes-verticas-first-officially-supported-open-source-database-client/). Please check out [release notes](https://github.com/vertica/vertica-python/releases) to learn about the latest improvements. vertica-python is currently in beta stage; it has been tested for functionality and has a very basic test suite. Please use with caution, and feel free to submit issues and/or pull requests (Read up on our [contributing guidelines](#contributing-guidelines)). vertica-python has been tested with Vertica 9.1.1 and Python 2.7/3.4/3.5/3.6/3.7/3.8. ## Installation To install vertica-python with pip: pip install vertica-python To install vertica-python from source, run the following command from the root directory: python setup.py install Source code for vertica-python can be found at: https://github.com/vertica/vertica-python #### Using Kerberos authentication vertica-python has optional Kerberos authentication support for Unix-like systems, which requires you to install the [kerberos](https://pypi.org/project/kerberos/) package: pip install kerberos Note that `kerberos` is a python extension module, which means you need to install `python-dev`. The command depends on the package manager and will look like sudo [yum|apt-get|etc] install python-dev ## Usage **Create connection** ```python import vertica_python conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', # autogenerated session label by default, 'session_label': 'some_label', # default throw error on invalid UTF-8 results 'unicode_error': 'strict', # SSL is disabled by default 'ssl': False, # using server-side prepared statements is disabled by default 'use_prepared_statements': False, # connection timeout is not enabled by default # 5 seconds timeout for a socket operation (Establishing a TCP connection or read/write operation) 'connection_timeout': 5} # simple connection, with manual close connection = vertica_python.connect(**conn_info) # do things connection.close() # using with for auto connection closing after usage with vertica_python.connect(**conn_info) as connection: # do things ``` You can pass an `ssl.SSLContext` to `ssl` to customize the SSL connection options. For example, ```python import vertica_python import ssl ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) ssl_context.verify_mode = ssl.CERT_REQUIRED ssl_context.check_hostname = True ssl_context.load_verify_locations(cafile='/path/to/ca_file.pem') conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'ssl': ssl_context} connection = vertica_python.connect(**conn_info) ``` See more on SSL options [here](https://docs.python.org/3.8/library/ssl.html). In order to use Kerberos authentication, install [dependencies](#using-kerberos-authentication) first, and it is the user's responsibility to ensure that an Ticket-Granting Ticket (TGT) is available and valid. Whether a TGT is available can be easily determined by running the `klist` command. If no TGT is available, then it first must be obtained by running the `kinit` command or by logging in. You can pass in optional arguments to customize the authentication. The arguments are `kerberos_service_name`, which defaults to "vertica", and `kerberos_host_name`, which defaults to the value of argument `host`. For example, ```python import vertica_python conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', # The service name portion of the Vertica Kerberos principal 'kerberos_service_name': 'vertica_krb', # The instance or host name portion of the Vertica Kerberos principal 'kerberos_host_name': 'vcluster.example.com'} with vertica_python.connect(**conn_info) as conn: # do things ``` Logging is disabled by default if you do not pass values to both ```log_level``` and ```log_path```. The default value of ```log_level``` is logging.WARNING. You can find all levels [here](https://docs.python.org/3.8/library/logging.html#logging-levels). The default value of ```log_path``` is 'vertica_python.log', the log file will be in the current execution directory. If ```log_path``` is set to ```None``` no file handler is set, logs will be processed by root handlers. For example, ```python import vertica_python import logging ## Example 1: write DEBUG level logs to './vertica_python.log' conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'log_level': logging.DEBUG} with vertica_python.connect(**conn_info) as connection: # do things ## Example 2: write WARNING level logs to './path/to/logs/client.log' conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'log_path': 'path/to/logs/client.log'} with vertica_python.connect(**conn_info) as connection: # do things ## Example 3: write INFO level logs to '/home/admin/logs/vClient.log' conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'log_level': logging.INFO, 'log_path': '/home/admin/logs/vClient.log'} with vertica_python.connect(**conn_info) as connection: # do things ## Example 4: use root handlers to process logs by setting 'log_path' to 'None' conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'log_level': logging.DEBUG, 'log_path': None} with vertica_python.connect(**conn_info) as connection: # do things ``` Connection Failover: Supply a list of backup hosts to ```backup_server_node``` for the client to try if the primary host you specify in the connection parameters (```host```, ```port```) is unreachable. Each item in the list should be either a host string (using default port 5433) or a (host, port) tuple. A host can be a host name or an IP address. ```python import vertica_python conn_info = {'host': 'unreachable.server.com', 'port': 888, 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'backup_server_node': ['123.456.789.123', 'invalid.com', ('10.20.82.77', 6000)]} connection = vertica_python.connect(**conn_info) ``` Connection Load Balancing helps automatically spread the overhead caused by client connections across the cluster by having hosts redirect client connections to other hosts. Both the server and the client need to enable load balancing for it to function. If the server disables connection load balancing, the load balancing request from client will be ignored. ```python import vertica_python conn_info = {'host': '127.0.0.1', 'port': 5433, 'user': 'some_user', 'password': 'some_password', 'database': 'vdb', 'connection_load_balance': True} # Server enables load balancing with vertica_python.connect(**conn_info) as conn: cur = conn.cursor() cur.execute("SELECT NODE_NAME FROM V_MONITOR.CURRENT_SESSION") print("Client connects to primary node:", cur.fetchone()[0]) cur.execute("SELECT SET_LOAD_BALANCE_POLICY('ROUNDROBIN')") with vertica_python.connect(**conn_info) as conn: cur = conn.cursor() cur.execute("SELECT NODE_NAME FROM V_MONITOR.CURRENT_SESSION") print("Client redirects to node:", cur.fetchone()[0]) ## Output # Client connects to primary node: v_vdb_node0003 # Client redirects to node: v_vdb_node0005 ``` Another way to set connection properties is passing a connection string to the keyword parameter `dsn` of `vertica_python.connect(dsn='...', **kwargs)`. The connection string is of the form: ``` vertica://(user):(password)@(host):(port)/(database)?(arg1=val1&arg2=val2&...) ``` The connection string would be parsed by `vertica_python.parse_dsn(connection_str)`, and the parsing result (a dictionary of keywords and values) would be merged with _kwargs_. If the same keyword is specified in both the sources, the _kwargs_ value overrides the parsed _dsn_ value. The `(arg1=val1&arg2=val2&...)` section can handle string/numeric/boolean values, blank and invalid value would be ignored. ```python import vertica_python connection_str = ('vertica://admin@localhost:5433/db1?connection_load_balance=True&connection_timeout=1.5&' 'session_label=vpclient+123%7E456') print(vertica_python.parse_dsn(connection_str)) # {'user': 'admin', 'host': 'localhost', 'port': 5433, 'database': 'db1', # 'connection_load_balance': True, 'connection_timeout': 1.5, 'session_label': 'vpclient 123~456'} additional_info = { 'password': 'some_password', 'backup_server_node': ['10.6.7.123', ('10.20.82.77', 6000)] # invalid value to be set in a connection string } with vertica_python.connect(dsn=connection_str, **additional_info) as conn: # do things ``` **Stream query results**: ```python cur = connection.cursor() cur.execute("SELECT * FROM a_table LIMIT 2") for row in cur.iterate(): print(row) # [ 1, 'some text', datetime.datetime(2014, 5, 18, 6, 47, 1, 928014) ] # [ 2, 'something else', None ] ``` Streaming is recommended if you want to further process each row, save the results in a non-list/dict format (e.g. Pandas DataFrame), or save the results in a file. **In-memory results as list**: ```python cur = connection.cursor() cur.execute("SELECT * FROM a_table LIMIT 2") cur.fetchall() # [ [1, 'something'], [2, 'something_else'] ] ``` **In-memory results as dictionary**: ```python cur = connection.cursor('dict') cur.execute("SELECT * FROM a_table LIMIT 2") cur.fetchall() # [ {'id': 1, 'value': 'something'}, {'id': 2, 'value': 'something_else'} ] connection.close() ``` **Query using named parameters**: ```python cur = connection.cursor() cur.execute("SELECT * FROM a_table WHERE a = :propA b = :propB", {'propA': 1, 'propB': 'stringValue'}) cur.fetchall() # [ [1, 'something'], [2, 'something_else'] ] ``` **Query using server-side prepared statements**: Vertica server-side prepared statements let you define a statement once and then run it many times with different parameters. Placeholders in the statement are represented by question marks (?). Server-side prepared statements are useful for preventing SQL injection attacks. ```python import vertica_python # Enable using server-side prepared statements at connection level conn_info = {'host': '127.0.0.1', 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'use_prepared_statements': True, } with vertica_python.connect(**conn_info) as connection: cur = connection.cursor() cur.execute("CREATE TABLE tbl (a INT, b VARCHAR)") cur.execute("INSERT INTO tbl VALUES (?, ?)", [1, 'aa']) cur.execute("INSERT INTO tbl VALUES (?, ?)", [2, 'bb']) cur.executemany("INSERT INTO tbl VALUES (?, ?)", [(3, 'foo'), (4, 'xx'), (5, 'bar')]) cur.execute("COMMIT") cur.execute("SELECT * FROM tbl WHERE a>=? AND a<=? ORDER BY a", (2,4)) cur.fetchall() # [[2, 'bb'], [3, 'foo'], [4, 'xx']] ``` Vertica does not support executing a command string containing multiple statements using server-side prepared statements. You can set ```use_prepared_statements``` option in ```cursor.execute*()``` functions to override the connection level setting. ```python import vertica_python # Enable using server-side prepared statements at connection level conn_info = {'host': '127.0.0.1', 'user': 'some_user', 'password': 'some_password', 'database': 'a_database', 'use_prepared_statements': True, } with vertica_python.connect(**conn_info) as connection: cur = connection.cursor() cur.execute("CREATE TABLE tbl (a INT, b VARCHAR)") # Executing compound statements cur.execute("INSERT INTO tbl VALUES (?, ?); COMMIT", [1, 'aa']) # Error message: Cannot insert multiple commands into a prepared statement # Disable prepared statements but forget to change placeholders (?) cur.execute("INSERT INTO tbl VALUES (?, ?); COMMIT;", [1, 'aa'], use_prepared_statements=False) # TypeError: not all arguments converted during string formatting cur.execute("INSERT INTO tbl VALUES (%s, %s); COMMIT;", [1, 'aa'], use_prepared_statements=False) cur.execute("INSERT INTO tbl VALUES (:a, :b); COMMIT;", {'a': 2, 'b': 'bb'}, use_prepared_statements=False) # Disable using server-side prepared statements at connection level conn_info['use_prepared_statements'] = False with vertica_python.connect(**conn_info) as connection: cur = connection.cursor() # Try using prepared statements cur.execute("INSERT INTO tbl VALUES (?, ?)", [3, 'foo']) # TypeError: not all arguments converted during string formatting cur.execute("INSERT INTO tbl VALUES (?, ?)", [3, 'foo'], use_prepared_statements=True) # Query using named parameters cur.execute("SELECT * FROM tbl WHERE a>=:n1 AND a<=:n2 ORDER BY a", {'n1': 2, 'n2': 4}) cur.fetchall() # [[2, 'bb'], [3, 'foo']] ``` Note: In other drivers, the batch insert is converted into a COPY statement by using prepared statements. vertica-python currently does not support that. **Insert and commits** : ```python cur = connection.cursor() # inline commit (when 'use_prepared_statements' is False) cur.execute("INSERT INTO a_table (a, b) VALUES (1, 'aa'); commit;") # commit in execution cur.execute("INSERT INTO a_table (a, b) VALUES (1, 'aa')") cur.execute("INSERT INTO a_table (a, b) VALUES (2, 'bb')") cur.execute("commit;") # connection.commit() cur.execute("INSERT INTO a_table (a, b) VALUES (1, 'aa')") connection.commit() ``` **Copy** : ```python cur = connection.cursor() cur.copy("COPY test_copy (id, name) from stdin DELIMITER ',' ", csv) ``` Where `csv` is either a string or a file-like object (specifically, any object with a `read()` method). If using a file, the data is streamed (in chunks of `buffer_size` bytes, which defaults to 128 * 2 ** 10). ```python with open("/tmp/binary_file.csv", "rb") as fs: cursor.copy("COPY table(field1, field2) FROM STDIN DELIMITER ',' ENCLOSED BY '\"'", fs, buffer_size=65536) ``` **Cancel the current database operation** : `Connection.cancel()` interrupts the processing of the current operation. Interrupting query execution will cause the cancelled method to raise a `vertica_python.errors.QueryCanceled`. If no query is being executed, it does nothing. You can call this function from a different thread/process than the one currently executing a database operation. ```python from multiprocessing import Process import time import vertica_python def cancel_query(connection, timeout=5): time.sleep(timeout) connection.cancel() with vertica_python.connect(**conn_info) as conn: cur = conn.cursor() # Call cancel() from a different process p1 = Process(target=cancel_query, args=(conn,)) p1.start() try: cur.execute("") except vertica_python.errors.QueryCanceled as e: pass p1.join() ``` ## Rowcount oddities vertica_python behaves a bit differently than dbapi when returning rowcounts. After a select execution, the rowcount will be -1, indicating that the row count is unknown. The rowcount value will be updated as data is streamed. ```python cur.execute('SELECT 10 things') cur.rowcount == -1 # indicates unknown rowcount cur.fetchone() cur.rowcount == 1 cur.fetchone() cur.rowcount == 2 cur.fetchall() cur.rowcount == 10 ``` After an insert/update/delete, the rowcount will be returned as a single element row: ```python cur.execute("DELETE 3 things") cur.rowcount == -1 # indicates unknown rowcount cur.fetchone()[0] == 3 ``` ## Nextset If you execute multiple statements in a single call to execute(), you can use cursor.nextset() to retrieve all of the data. ```python cur.execute('SELECT 1; SELECT 2;') cur.fetchone() # [1] cur.fetchone() # None cur.nextset() # True cur.fetchone() # [2] cur.fetchone() # None cur.nextset() # None ``` ## UTF-8 encoding issues While Vertica expects varchars stored to be UTF-8 encoded, sometimes invalid strings get into the database. You can specify how to handle reading these characters using the unicode_error connection option. This uses the same values as the unicode type (https://docs.python.org/2/library/functions.html#unicode) ```python cur = vertica_python.Connection({..., 'unicode_error': 'strict'}).cursor() cur.execute(r"SELECT E'\xC2'") cur.fetchone() # caught 'utf8' codec can't decode byte 0xc2 in position 0: unexpected end of data cur = vertica_python.Connection({..., 'unicode_error': 'replace'}).cursor() cur.execute(r"SELECT E'\xC2'") cur.fetchone() # � cur = vertica_python.Connection({..., 'unicode_error': 'ignore'}).cursor() cur.execute(r"SELECT E'\xC2'") cur.fetchone() # ``` ## License Apache 2.0 License, please see `LICENSE` for details. ## Contributing guidelines Have a bug or an idea? Please see `CONTRIBUTING.md` for details. ## Acknowledgements We would like to thank the contributors to the Ruby Vertica gem (https://github.com/sprsquish/vertica), as this project gave us inspiration and help in understanding Vertica's wire protocol. These contributors are: * [Matt Bauer](http://github.com/mattbauer) * [Jeff Smick](http://github.com/sprsquish) * [Willem van Bergen](http://github.com/wvanbergen) * [Camilo Lopez](http://github.com/camilo) vertica-python-0.10.1/Vagrantfile000066400000000000000000000016261357720411700167460ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : ENV["VAGRANT_DEFAULT_PROVIDER"] ||= "docker" VAGRANTFILE_API_VERSION = "2" ####################################################################### # This will set up a box with Vertica Community Edition 7.1.1 # running inside the box in a Docker container. # # The purpose is to have a Vertica instance that can be used by tests. # # Vertica's port 5433 is exposed to host machine. # Database 'docker' is available. # User is 'dbadmin' with no password. # # >>> # ! As is, any data stored inside Vertica will not live through # ! container or VM restart. # >>> ####################################################################### Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.provider "docker" do |d| d.image = "sumitchawla/vertica:latest" d.ports = ["5433:5433"] end config.vm.synced_folder ".", "/vagrant", disabled: true end vertica-python-0.10.1/requirements-dev.txt000066400000000000000000000000771357720411700206200ustar00rootroot00000000000000future pytest pytest-timeout python-dateutil six tox #kerberos vertica-python-0.10.1/setup.py000066400000000000000000000063131357720411700162710ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. #!/usr/bin/env python import collections from setuptools import setup, find_packages ReqOpts = collections.namedtuple('ReqOpts', ['skip_requirements_regex', 'default_vcs']) opts = ReqOpts(None, 'git') # version should use the format 'x.x.x' (instead of 'vx.x.x') setup( name='vertica-python', version='0.10.1', description='A native Python client for the Vertica database.', author='Justin Berka, Alex Kim, Siting Ren', author_email='justin.berka@gmail.com, alex.kim@uber.com, sitingren@hotmail.com', url='https://github.com/vertica/vertica-python', keywords="database vertica", packages=find_packages(), license="Apache License 2.0", install_requires=[ 'python-dateutil>=1.5', 'future', 'six>=1.10.0' ], classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Topic :: Database", "Topic :: Database :: Database Engines/Servers", "Operating System :: OS Independent" ] ) vertica-python-0.10.1/tox.ini000066400000000000000000000002711357720411700160670ustar00rootroot00000000000000[tox] envlist = py27,py34,py35,py36,py37,py38 [testenv] passenv = * commands = pytest {posargs} deps = pytest pytest-timeout parameterized python-dateutil mock vertica-python-0.10.1/vertica_python/000077500000000000000000000000001357720411700176125ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/__init__.py000066400000000000000000000062631357720411700217320ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .vertica.connection import Connection, connect, parse_dsn # Importing exceptions for compatibility with dbapi 2.0. # See: PEP 249 - Python Database API 2.0 # https://www.python.org/dev/peps/pep-0249/#exceptions from . import errors from .errors import ( Error, Warning, DataError, DatabaseError, IntegrityError, InterfaceError, InternalError, NotSupportedError, OperationalError, ProgrammingError) # Main module for this library. __author__ = 'Vertica' __copyright__ = 'Copyright (c) 2018-2019 Micro Focus or one of its affiliates.' __license__ = 'Apache 2.0' __all__ = ['Connection', 'PROTOCOL_VERSION', 'version_info', 'apilevel', 'threadsafety', 'paramstyle', 'connect', 'parse_dsn', 'Error', 'Warning', 'DataError', 'DatabaseError', 'IntegrityError', 'InterfaceError', 'InternalError', 'NotSupportedError', 'OperationalError', 'ProgrammingError'] # The version number of this library. version_info = (0, 10, 1) __version__ = '.'.join(map(str, version_info)) # The protocol version (3.8) implemented in this library. PROTOCOL_VERSION = 3 << 16 | 8 apilevel = 2.0 threadsafety = 1 # Threads may share the module, but not connections! # Accepted paramstyles are # 'qmark' = Question mark style, e.g. '...WHERE name=?' # 'named' = Named style, e.g. '...WHERE name=:name' # 'format' = ANSI C printf format codes, e.g. '...WHERE name=%s' paramstyle = 'named' vertica-python-0.10.1/vertica_python/compat.py000066400000000000000000000113331357720411700214500ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Copyright 2015 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== """Functions for Python 2 vs. 3 compatibility. ## Conversion routines In addition to the functions below, `as_str` converts an object to a `str`. @@as_bytes @@as_text @@as_str_any ## Types The compatibility module also provides the following types: * `bytes_or_text_types` * `complex_types` * `integral_types` * `real_types` """ from __future__ import absolute_import from __future__ import division from __future__ import print_function import six as _six def as_bytes(bytes_or_text, encoding='utf-8'): """Converts either bytes or unicode to `bytes`, using utf-8 encoding for text. Args: bytes_or_text: A `bytes`, `str`, or `unicode` object. encoding: A string indicating the charset for encoding unicode. Returns: A `bytes` object. Raises: TypeError: If `bytes_or_text` is not a binary or unicode string. """ if isinstance(bytes_or_text, _six.text_type): return bytes_or_text.encode(encoding) elif isinstance(bytes_or_text, bytes): return bytes_or_text else: raise TypeError('Expected binary or unicode string, got %r' % (bytes_or_text,)) def as_text(bytes_or_text, encoding='utf-8'): """Returns the given argument as a unicode string. Args: bytes_or_text: A `bytes`, `str, or `unicode` object. encoding: A string indicating the charset for decoding unicode. Returns: A `unicode` (Python 2) or `str` (Python 3) object. Raises: TypeError: If `bytes_or_text` is not a binary or unicode string. """ if isinstance(bytes_or_text, _six.text_type): return bytes_or_text elif isinstance(bytes_or_text, bytes): return bytes_or_text.decode(encoding) else: raise TypeError('Expected binary or unicode string, got %r' % bytes_or_text) # Convert an object to a `str` in both Python 2 and 3. if _six.PY2: as_str = as_bytes else: as_str = as_text def as_str_any(value): """Converts to `str` as `str(value)`, but use `as_str` for `bytes`. Args: value: A object that can be converted to `str`. Returns: A `str` object. """ if isinstance(value, bytes): return as_str(value) else: return str(value) # Either bytes or text. bytes_or_text_types = (bytes, _six.text_type) _allowed_symbols = [ 'as_str', 'bytes_or_text_types', ] vertica-python-0.10.1/vertica_python/datatypes.py000066400000000000000000000411001357720411700221560ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from datetime import date, datetime, time # noinspection PyPep8Naming def Date(year, month, day): return date(year, month, day) # noinspection PyPep8Naming def Time(hour, minute, second): return time(hour, minute, second) # noinspection PyPep8Naming def Timestamp(year, month, day, hour, minute, second): return datetime(year, month, day, hour, minute, second) # noinspection PyPep8Naming def DateFromTicks(ticks): d = datetime.utcfromtimestamp(ticks) return d.date() # noinspection PyPep8Naming def TimeFromTicks(ticks): d = datetime.utcfromtimestamp(ticks) return d.time() # noinspection PyPep8Naming def TimestampFromTicks(ticks): d = datetime.utcfromtimestamp(ticks) return d.time() class Bytea(str): pass # noinspection PyPep8Naming def Binary(string): return Bytea(string) class VerticaType(object): UNKNOWN = 4 BOOL = 5 INT8 = 6 FLOAT8 = 7 CHAR = 8 VARCHAR = 9 DATE = 10 TIME = 11 TIMESTAMP = 12 TIMESTAMPTZ = 13 INTERVAL = 14 INTERVALYM = 114 TIMETZ = 15 NUMERIC = 16 VARBINARY = 17 UUID = 20 LONGVARCHAR = 115 LONGVARBINARY = 116 BINARY = 117 def __init__(self, *values): self.values = values def __cmp__(self, other): if other in self.values: return 0 if other < self.values: return 1 else: return -1 def __eq__(self, other): return other in self.values def __ne__(self, other): return other not in self.values # dbapi: type object used to describe columns that are string-based STRING = VerticaType(VerticaType.CHAR, VerticaType.VARCHAR, VerticaType.BINARY, VerticaType.VARBINARY, VerticaType.UNKNOWN, VerticaType.LONGVARBINARY, VerticaType.LONGVARCHAR) # dbapi: type object used to describe (long) binary columns BINARY = VerticaType(VerticaType.BINARY, VerticaType.VARBINARY, VerticaType.LONGVARBINARY) # dbapi: type object used to describe numeric columns NUMBER = VerticaType(VerticaType.INT8, VerticaType.FLOAT8, VerticaType.NUMERIC) # dbapi: type object used to describe date/time columns DATETIME = VerticaType(VerticaType.TIME, VerticaType.TIMETZ, VerticaType.TIMESTAMP, VerticaType.TIMESTAMPTZ, VerticaType.INTERVAL, VerticaType.INTERVALYM) # dbapi: type object used to describe the "Row ID" column ROWID = VerticaType() # Vertica doesn't have row_id type # the max size of a CHAR/VARCHAR/BINARY/VARBINARY MAX_STRING_LEN = 65000 # the max size of a LONG VARCHAR/LONG VARBINARY MAX_LONG_STRING_LEN = 32000000 # interval mask constants # these masks determine the range of an interval for a given type_modifier # for example, an interval has range "Day to Hour" if: # (type_modifier & INTERVAL_MASK_DAY2HOUR) == INTERVAL_MASK_DAY2HOUR INTERVAL_MASK_MONTH = 1 << 17 INTERVAL_MASK_YEAR = 1 << 18 INTERVAL_MASK_DAY = 1 << 19 INTERVAL_MASK_HOUR = 1 << 26 INTERVAL_MASK_MINUTE = 1 << 27 INTERVAL_MASK_SECOND = 1 << 28 INTERVAL_MASK_YEAR2MONTH = INTERVAL_MASK_YEAR | INTERVAL_MASK_MONTH INTERVAL_MASK_DAY2HOUR = INTERVAL_MASK_DAY | INTERVAL_MASK_HOUR INTERVAL_MASK_DAY2MIN = INTERVAL_MASK_DAY | INTERVAL_MASK_HOUR | INTERVAL_MASK_MINUTE INTERVAL_MASK_DAY2SEC = INTERVAL_MASK_DAY | INTERVAL_MASK_HOUR | INTERVAL_MASK_MINUTE | INTERVAL_MASK_SECOND INTERVAL_MASK_HOUR2MIN = INTERVAL_MASK_HOUR | INTERVAL_MASK_MINUTE INTERVAL_MASK_HOUR2SEC = INTERVAL_MASK_HOUR | INTERVAL_MASK_MINUTE | INTERVAL_MASK_SECOND INTERVAL_MASK_MIN2SEC = INTERVAL_MASK_MINUTE | INTERVAL_MASK_SECOND def getTypeName(data_type_oid, type_modifier): """Returns the base type name according to data_type_oid and type_modifier""" if data_type_oid == VerticaType.BOOL: return "Boolean" elif data_type_oid == VerticaType.INT8: return "Integer" elif data_type_oid == VerticaType.FLOAT8: return "Float" elif data_type_oid == VerticaType.CHAR: return "Char" elif data_type_oid in (VerticaType.VARCHAR, VerticaType.UNKNOWN): return "Varchar" elif data_type_oid == VerticaType.LONGVARCHAR: return "Long Varchar" elif data_type_oid == VerticaType.DATE: return "Date" elif data_type_oid == VerticaType.TIME: return "Time" elif data_type_oid == VerticaType.TIMETZ: return "TimeTz" elif data_type_oid == VerticaType.TIMESTAMP: return "Timestamp" elif data_type_oid == VerticaType.TIMESTAMPTZ: return "TimestampTz" elif data_type_oid in (VerticaType.INTERVAL, VerticaType.INTERVALYM): return "Interval " + getIntervalRange(data_type_oid, type_modifier) elif data_type_oid == VerticaType.BINARY: return "Binary" elif data_type_oid == VerticaType.VARBINARY: return "Varbinary" elif data_type_oid == VerticaType.LONGVARBINARY: return "Long Varbinary" elif data_type_oid == VerticaType.NUMERIC: return "Numeric" elif data_type_oid == VerticaType.UUID: return "Uuid" else: return "Unknown" def getIntervalRange(data_type_oid, type_modifier): """Extracts an interval's range from the bits set in its type_modifier""" if data_type_oid not in (VerticaType.INTERVAL, VerticaType.INTERVALYM): raise ValueError("Invalid data type OID: {}".format(data_type_oid)) if type_modifier == -1: # assume the default if data_type_oid == VerticaType.INTERVALYM: return "Year to Month" elif data_type_oid == VerticaType.INTERVAL: return "Day to Second" if data_type_oid == VerticaType.INTERVALYM: # Year/Month intervals if (type_modifier & INTERVAL_MASK_YEAR2MONTH) == INTERVAL_MASK_YEAR2MONTH: return "Year to Month" elif (type_modifier & INTERVAL_MASK_YEAR) == INTERVAL_MASK_YEAR: return "Year" elif (type_modifier & INTERVAL_MASK_MONTH) == INTERVAL_MASK_MONTH: return "Month" else: return "Year to Month" if data_type_oid == VerticaType.INTERVAL: # Day/Time intervals if (type_modifier & INTERVAL_MASK_DAY2SEC) == INTERVAL_MASK_DAY2SEC: return "Day to Second" elif (type_modifier & INTERVAL_MASK_DAY2MIN) == INTERVAL_MASK_DAY2MIN: return "Day to Minute" elif (type_modifier & INTERVAL_MASK_DAY2HOUR) == INTERVAL_MASK_DAY2HOUR: return "Day to Hour" elif (type_modifier & INTERVAL_MASK_DAY) == INTERVAL_MASK_DAY: return "Day" elif (type_modifier & INTERVAL_MASK_HOUR2SEC) == INTERVAL_MASK_HOUR2SEC: return "Hour to Second" elif (type_modifier & INTERVAL_MASK_HOUR2MIN) == INTERVAL_MASK_HOUR2MIN: return "Hour to Minute" elif (type_modifier & INTERVAL_MASK_HOUR) == INTERVAL_MASK_HOUR: return "Hour" elif (type_modifier & INTERVAL_MASK_MIN2SEC) == INTERVAL_MASK_MIN2SEC: return "Minute to Second" elif (type_modifier & INTERVAL_MASK_MINUTE) == INTERVAL_MASK_MINUTE: return "Minute" elif (type_modifier & INTERVAL_MASK_SECOND) == INTERVAL_MASK_SECOND: return "Second" else: return "Day to Second" def getIntervalLeadingPrecision(data_type_oid, type_modifier): """ Returns the leading precision for an interval, which is the largest number of digits that can fit in the leading field of the interval. All Year/Month intervals are defined in terms of months, even if the type_modifier forbids months to be specified (i.e. INTERVAL YEAR). Similarly, all Day/Time intervals are defined as a number of microseconds. Because of this, interval types with shorter ranges will have a larger leading precision. For example, an INTERVAL DAY's leading precision is ((2^63)-1)/MICROSECS_PER_DAY, while an INTERVAL HOUR's leading precision is ((2^63)-1)/MICROSECS_PER_HOUR """ interval_range = getIntervalRange(data_type_oid, type_modifier) if interval_range in ("Year", "Year to Month"): return 18 elif interval_range == "Month": return 19 elif interval_range in ("Day", "Day to Hour", "Day to Minute", "Day to Second"): return 9 elif interval_range in ("Hour", "Hour to Minute", "Hour to Second"): return 10 elif interval_range in ("Minute", "Minute to Second"): return 12 elif interval_range == "Second": return 13 else: raise ValueError("Invalid interval range: {}".format(interval_range)) def getPrecision(data_type_oid, type_modifier): """ Returns the precision for the given Vertica type with consideration of the type modifier. For numerics, precision is the total number of digits (in base 10) that can fit in the type. For intervals, time and timestamps, precision is the number of digits to the right of the decimal point in the seconds portion of the time. The type modifier of -1 is used when the size of a type is unknown. In those cases we assume the maximum possible size. """ if data_type_oid == VerticaType.NUMERIC: if type_modifier == -1: return 1024 return ((type_modifier - 4) >> 16) & 0xFFFF elif data_type_oid in (VerticaType.TIME, VerticaType.TIMETZ, VerticaType.TIMESTAMP, VerticaType.TIMESTAMPTZ, VerticaType.INTERVAL, VerticaType.INTERVALYM): if type_modifier == -1: return 6 return type_modifier & 0xF else: return None # None if no meaningful values can be provided def getScale(data_type_oid, type_modifier): """ Returns the scale for the given Vertica type with consideration of the type modifier. """ if data_type_oid == VerticaType.NUMERIC: return 15 if type_modifier == -1 else (type_modifier - 4) & 0xFF else: return None # None if no meaningful values can be provided def getDisplaySize(data_type_oid, type_modifier): """ Returns the column display size for the given Vertica type with consideration of the type modifier. The display size of a column is the maximum number of characters needed to display data in character form. """ if data_type_oid == VerticaType.BOOL: # T or F return 1 elif data_type_oid == VerticaType.INT8: # a sign and 19 digits if signed or 20 digits if unsigned return 20 elif data_type_oid == VerticaType.FLOAT8: # a sign, 15 digits, a decimal point, the letter E, a sign, and 3 digits return 22 elif data_type_oid == VerticaType.NUMERIC: # a sign, precision digits, and a decimal point return getPrecision(data_type_oid, type_modifier) + 2 elif data_type_oid == VerticaType.DATE: # yyyy-mm-dd, a space, and the calendar era (BC) return 13 elif data_type_oid == VerticaType.TIME: seconds_precision = getPrecision(data_type_oid, type_modifier) if seconds_precision == 0: # hh:mm:ss return 8 else: # hh:mm:ss.[fff...] return 9 + seconds_precision elif data_type_oid == VerticaType.TIMETZ: seconds_precision = getPrecision(data_type_oid, type_modifier) if seconds_precision == 0: # hh:mm:ss, a sign, hh:mm return 14 else: # hh:mm:ss.[fff...], a sign, hh:mm return 15 + seconds_precision elif data_type_oid == VerticaType.TIMESTAMP: seconds_precision = getPrecision(data_type_oid, type_modifier) if seconds_precision == 0: # yyyy-mm-dd hh:mm:ss, a space, and the calendar era (BC) return 22 else: # yyyy-mm-dd hh:mm:ss[.fff...], a space, and the calendar era (BC) return 23 + seconds_precision elif data_type_oid == VerticaType.TIMESTAMPTZ: seconds_precision = getPrecision(data_type_oid, type_modifier) if seconds_precision == 0: # yyyy-mm-dd hh:mm:ss, a sign, hh:mm, a space, and the calendar era (BC) return 28 else: # yyyy-mm-dd hh:mm:ss.[fff...], a sign, hh:mm, a space, and the calendar era (BC) return 29 + seconds_precision elif data_type_oid in (VerticaType.INTERVAL, VerticaType.INTERVALYM): leading_precision = getIntervalLeadingPrecision(data_type_oid, type_modifier) seconds_precision = getPrecision(data_type_oid, type_modifier) interval_range = getIntervalRange(data_type_oid, type_modifier) if interval_range in ("Year", "Month", "Day", "Hour", "Minute"): # a sign, [range...] return 1 + leading_precision elif interval_range in ("Day to Hour", "Year to Month", "Hour to Minute"): # a sign, [dd...] hh; a sign, [yy...]-mm; a sign, [hh...]:mm return 1 + leading_precision + 3 elif interval_range == "Day to Minute": # a sign, [dd...] hh:mm return 1 + leading_precision + 6 elif interval_range == "Second": if seconds_precision == 0: # a sign, [ss...] return 1 + leading_precision else: # a sign, [ss...].[fff...] return 1 + leading_precision + 1 + seconds_precision elif interval_range == "Day to Second": if seconds_precision == 0: # a sign, [dd...] hh:mm:ss return 1 + leading_precision + 9 else: # a sign, [dd...] hh:mm:ss.[fff...] return 1 + leading_precision + 10 + seconds_precision elif interval_range == "Hour to Second": if seconds_precision == 0: # a sign, [hh...]:mm:ss return 1 + leading_precision + 6 else: # a sign, [hh...]:mm:ss.[fff...] return 1 + leading_precision + 7 + seconds_precision elif interval_range == "Minute to Second": if seconds_precision == 0: # a sign, [mm...]:ss return 1 + leading_precision + 3 else: # a sign, [mm...]:ss.[fff...] return 1 + leading_precision + 4 + seconds_precision elif data_type_oid == VerticaType.UUID: # aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee return 36 elif data_type_oid in (VerticaType.CHAR, VerticaType.VARCHAR, VerticaType.BINARY, VerticaType.VARBINARY, VerticaType.UNKNOWN): # the defined maximum octet length of the column return MAX_STRING_LEN if type_modifier <= -1 else (type_modifier - 4) elif data_type_oid in (VerticaType.LONGVARCHAR, VerticaType.LONGVARBINARY): return MAX_LONG_STRING_LEN if type_modifier <= -1 else (type_modifier - 4) else: return None # None if no meaningful values can be provided vertica-python-0.10.1/vertica_python/errors.py000066400000000000000000000122171357720411700215030ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import re from .vertica.mixins.notice_response_attr import _NoticeResponseAttrMixin ############################################# # dbapi errors ############################################# class Error(Exception): pass # noinspection PyShadowingBuiltins class Warning(Exception): pass class InterfaceError(Error): pass class DatabaseError(Error): pass class InternalError(DatabaseError): pass class OperationalError(DatabaseError): pass class ProgrammingError(DatabaseError): pass class IntegrityError(DatabaseError): pass class DataError(DatabaseError): pass class NotSupportedError(DatabaseError): pass # # Other Errors # class TimedOutError(OperationalError): pass class ConnectionError(DatabaseError): pass class KerberosError(ConnectionError): pass class SSLNotSupported(ConnectionError): pass class MessageError(InternalError): pass class EmptyQueryError(ProgrammingError): pass class QueryError(_NoticeResponseAttrMixin, ProgrammingError): """ An error occurred associated with a query had been issued. This error is perhaps the most commonly encountered error type, associated with failures during query execution, invalid SQL statements, and more. Much of the details of the error are available as properties, including the SQL statement that had been issued (`sql`). """ def __init__(self, error_response, sql): self.error_response = error_response self.sql = sql ProgrammingError.__init__(self, "{0}, SQL: {1}".format(error_response.error_message(), repr(self.one_line_sql()))) def one_line_sql(self): if self.sql: return re.sub(r"[\r\n]+", ' ', self.sql) else: return '' @property def _notice_attrs(self): # provided for _NoticeResponseAttrMixin if self.error_response is None: return {} return self.error_response._notice_attrs or {} @classmethod def from_error_response(cls, error_response, sql): klass = QUERY_ERROR_CLASSES.get(error_response.sqlstate, None) if klass is None: klass = cls return klass(error_response, sql) class LockFailure(QueryError): pass class InsufficientResources(QueryError): pass class OutOfMemory(QueryError): pass class VerticaSyntaxError(QueryError): pass class MissingSchema(QueryError): pass class MissingRelation(QueryError): pass class MissingColumn(QueryError): pass class CopyRejected(QueryError): pass class PermissionDenied(QueryError): pass class InvalidDatetimeFormat(QueryError): pass class DuplicateObject(QueryError): pass class QueryCanceled(QueryError): pass class ConnectionFailure(QueryError): pass class LostConnectivityFailure(QueryError): pass QUERY_ERROR_CLASSES = { '55V03': LockFailure, '53000': InsufficientResources, '53200': OutOfMemory, '42601': VerticaSyntaxError, '3F000': MissingSchema, '42V01': MissingRelation, '42703': MissingColumn, '22V04': CopyRejected, '42501': PermissionDenied, '22007': InvalidDatetimeFormat, '42710': DuplicateObject, '57014': QueryCanceled, '08006': ConnectionFailure, 'V1001': LostConnectivityFailure } vertica-python-0.10.1/vertica_python/tests/000077500000000000000000000000001357720411700207545ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/tests/__init__.py000066400000000000000000000033411357720411700230660ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.vertica-python-0.10.1/vertica_python/tests/common/000077500000000000000000000000001357720411700222445ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/tests/common/__init__.py000066400000000000000000000033411357720411700243560ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.vertica-python-0.10.1/vertica_python/tests/common/base.py000066400000000000000000000146051357720411700235360ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import os import sys import logging import unittest import inspect import getpass import six if six.PY2: from ConfigParser import ConfigParser elif six.PY3: from configparser import ConfigParser from ...compat import as_text, as_str, as_bytes from ...vertica.log import VerticaLogging default_configs = { 'log_dir': 'vp_test_log', 'log_level': logging.WARNING, 'host': 'localhost', 'port': 5433, 'user': getpass.getuser(), 'password': '', } class VerticaPythonTestCase(unittest.TestCase): """ Base class for all tests """ @classmethod def _load_test_config(cls, config_list): test_config = {} # load default configurations for key in config_list: if key != 'database': test_config[key] = default_configs[key] # override with the configuration file confparser = ConfigParser() confparser.optionxform = str SECTION = 'vp_test_config' # section name in the configuration file # the configuration file is placed in the same directory as this file conf_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vp_test.conf') confparser.read(conf_file) for k in config_list: option = 'VP_TEST_' + k.upper() if confparser.has_option(SECTION, option): test_config[k] = confparser.get(SECTION, option) # override again with VP_TEST_* environment variables for k in config_list: env = 'VP_TEST_' + k.upper() if env in os.environ: test_config[k] = os.environ[env] # data preprocessing # value is string when loaded from configuration file and environment variable if 'port' in test_config: test_config['port'] = int(test_config['port']) if 'database' in config_list and 'user' in test_config: test_config.setdefault('database', test_config['user']) if 'log_level' in test_config: levels = ['NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] if isinstance(test_config['log_level'], str): if test_config['log_level'] not in levels: raise ValueError("Invalid value for VP_TEST_LOG_LEVEL: '{}'".format(test_config['log_level'])) test_config['log_level'] = eval('logging.' + test_config['log_level']) if 'log_dir' in test_config: test_config['log_dir'] = os.path.join(test_config['log_dir'], 'py{0}{1}'.format(sys.version_info.major, sys.version_info.minor)) return test_config @classmethod def _setup_logger(cls, tag, log_dir, log_level): # Setup test logger # E.g. If the class is defined in tests/integration_tests/test_dates.py # and test cases run under python3.7, then # the log would write to $VP_TEST_LOG_DIR/py37/integration_tests/test_dates.log testfile = os.path.splitext(os.path.basename(inspect.getsourcefile(cls)))[0] logfile = os.path.join(log_dir, tag, testfile + '.log') VerticaLogging.setup_logging(cls.__name__, logfile, log_level, cls.__name__) cls.logger = logging.getLogger(cls.__name__) return logfile def setUp(self): self.logger.info('\n\n'+'-'*50+'\n Begin '+self.__class__.__name__+"."+self._testMethodName+'\n'+'-'*50) def tearDown(self): self.logger.info('\n'+'-'*10+' End '+self.__class__.__name__+"."+self._testMethodName+' '+'-'*10+'\n') # Common assertions def assertTextEqual(self, first, second, msg=None): first_text = as_text(first) second_text = as_text(second) self.assertEqual(first=first_text, second=second_text, msg=msg) def assertStrEqual(self, first, second, msg=None): first_str = as_str(first) second_str = as_str(second) self.assertEqual(first=first_str, second=second_str, msg=msg) def assertBytesEqual(self, first, second, msg=None): first_bytes = as_bytes(first) second_bytes = as_bytes(second) self.assertEqual(first=first_bytes, second=second_bytes, msg=msg) def assertResultEqual(self, value, result, msg=None): if isinstance(value, six.string_types): self.assertTextEqual(first=value, second=result, msg=msg) else: self.assertEqual(first=value, second=result, msg=msg) def assertListOfListsEqual(self, list1, list2, msg=None): self.assertEqual(len(list1), len(list2), msg=msg) for l1, l2 in zip(list1, list2): self.assertListEqual(l1, l2, msg=msg) vertica-python-0.10.1/vertica_python/tests/common/vp_test.conf.example000066400000000000000000000007621357720411700262360ustar00rootroot00000000000000# To override default configuration information for the driver test system, # create a new file called 'vp_test.conf' in this directory with the following syntax [vp_test_config] # Connection information VP_TEST_HOST=10.0.0.2 VP_TEST_PORT=5000 VP_TEST_USER=dbadmin #VP_TEST_DATABASE=vdb1 #VP_TEST_PASSWORD=abcdef1234 # Logging information # Valid VP_TEST_LOG_LEVEL options: 'NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' VP_TEST_LOG_LEVEL=DEBUG VP_TEST_LOG_DIR=mylog/vp_tox_tests_log vertica-python-0.10.1/vertica_python/tests/conftest.py000066400000000000000000000015131357720411700231530ustar00rootroot00000000000000# Copyright (c) 2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. def pytest_configure(config): config.addinivalue_line( "markers", "integration_tests: mark test to be an integration test" ) config.addinivalue_line( "markers", "unit_tests: mark test to be an unit test" ) vertica-python-0.10.1/vertica_python/tests/integration_tests/000077500000000000000000000000001357720411700245215ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/tests/integration_tests/__init__.py000066400000000000000000000033411357720411700266330ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.vertica-python-0.10.1/vertica_python/tests/integration_tests/base.py000066400000000000000000000166461357720411700260220ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import pytest from ... import errors, connect from ..common.base import VerticaPythonTestCase @pytest.mark.integration_tests class VerticaPythonIntegrationTestCase(VerticaPythonTestCase): """ Base class for tests that connect to a Vertica database to run stuffs. This class is responsible for managing the environment variables and connection info used for all the tests, and provides support code to do common assertions and execute common queries. """ @classmethod def setUpClass(cls): config_list = ['log_dir', 'log_level', 'host', 'port', 'user', 'password', 'database'] cls.test_config = cls._load_test_config(config_list) # Test logger logfile = cls._setup_logger('integration_tests', cls.test_config['log_dir'], cls.test_config['log_level']) # Connection info # Note: The server-side prepared statements is disabled here. Please # see cls.createPrepStmtClass() below. cls._conn_info = { 'host': cls.test_config['host'], 'port': cls.test_config['port'], 'database': cls.test_config['database'], 'user': cls.test_config['user'], 'password': cls.test_config['password'], 'log_level': cls.test_config['log_level'], 'log_path': logfile, } cls.db_node_num = cls._get_node_num() cls.logger.info("Number of database node(s) = {}".format(cls.db_node_num)) @classmethod def tearDownClass(cls): pass @classmethod def _connect(cls): """Connects to vertica. :return: a connection to vertica. """ return connect(**cls._conn_info) @classmethod def _get_node_num(cls): """Executes a query to get the number of nodes in the database :return: the number of database nodes """ with cls._connect() as conn: cur = conn.cursor() cur.execute("SELECT count(*) FROM nodes WHERE node_state='UP'") return cur.fetchone()[0] @classmethod def createPrepStmtClass(cls): """Generates the code of a new subclass that has the same tests as this class but turns on the server-side prepared statements. To ensure test coverage, this method should be used if tests are not sensitive to paramstyles (or query protocols). Usage: "exec(xxxTestCase.createPrepStmtClass())" :return: a string acceptable by exec() to define the class """ base_cls_name = cls.__name__ cls_name = 'PrepStmt' + base_cls_name code = ('class ' + cls_name + '(' + base_cls_name + '):\n' ' @classmethod\n' ' def setUpClass(cls):\n' ' super(' + cls_name + ', cls).setUpClass()\n' " cls._conn_info['use_prepared_statements'] = True") return code def _query_and_fetchall(self, query): """Creates a new connection, executes a query and fetches all the results. :param query: query to execute :return: all fetched results as returned by cursor.fetchall() """ with self._connect() as conn: cur = conn.cursor() cur.execute(query) results = cur.fetchall() return results def _query_and_fetchone(self, query): """Creates a new connection, executes a query and fetches one result. :param query: query to execute :return: the first result fetched by cursor.fetchone() """ with self._connect() as conn: cur = conn.cursor() cur.execute(query) result = cur.fetchone() return result # Common assertions def assertConnectionFail(self, err_type=errors.ConnectionError, err_msg='Failed to establish a connection to the primary server or any backup address.'): with pytest.raises(err_type, match=err_msg): with self._connect() as conn: pass def assertConnectionSuccess(self): try: with self._connect() as conn: pass except Exception as e: self.fail('Connection failed: {0}'.format(e)) # Some tests require server-side setup # In that case, tests that depend on that setup should be skipped to prevent false failures # Tests that depend on the server-setup should call these methods to express requirements def require_DB_nodes_at_least(self, min_node_num): if not isinstance(min_node_num, int): err_msg = "Node number '{0}' must be an instance of 'int'".format(min_node_num) raise TypeError(err_msg) if min_node_num <= 0: err_msg = "Node number {0} must be a positive integer".format(min_node_num) raise ValueError(err_msg) if self.db_node_num < min_node_num: msg = ("The test requires a database that has at least {0} node(s), " "but this database has only {1} available node(s).").format( min_node_num, self.db_node_num) self.skipTest(msg) def require_protocol_at_least(self, min_protocol_version): with self._connect() as conn: effective_protocol = conn.parameters['protocol_version'] if effective_protocol < min_protocol_version: msg = ("The test requires the effective protocol version to be at " "least {}.{}, but the current version is {}.{}.").format( min_protocol_version >> 16, min_protocol_version & 0x0000ffff, effective_protocol >> 16, effective_protocol & 0x0000ffff) self.skipTest(msg) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_authentication.py000066400000000000000000000132461357720411700311570ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import from .base import VerticaPythonIntegrationTestCase class AuthenticationTestCase(VerticaPythonIntegrationTestCase): def setUp(self): super(AuthenticationTestCase, self).setUp() self._user = self._conn_info['user'] self._password = self._conn_info['password'] def tearDown(self): self._conn_info['user'] = self._user self._conn_info['password'] = self._password super(AuthenticationTestCase, self).tearDown() def test_SHA512(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP USER IF EXISTS sha512_user") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv4hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv6hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testlocalHash CASCADE") try: cur.execute("CREATE USER sha512_user IDENTIFIED BY 'password'") cur.execute("ALTER USER sha512_user SECURITY_ALGORITHM 'SHA512'") cur.execute("CREATE AUTHENTICATION testIPv4hostHash METHOD 'hash' HOST '0.0.0.0/0'") cur.execute("GRANT AUTHENTICATION testIPv4hostHash TO sha512_user") cur.execute("CREATE AUTHENTICATION testIPv6hostHash METHOD 'hash' HOST '::/0'") cur.execute("GRANT AUTHENTICATION testIPv6hostHash TO sha512_user") cur.execute("CREATE AUTHENTICATION testlocalHash METHOD 'hash' LOCAL") cur.execute("GRANT AUTHENTICATION testlocalHash TO sha512_user") self._conn_info['user'] = 'sha512_user' self._conn_info['password'] = 'password' expire_msg = 'The password for user sha512_user has expired' self.assertConnectionFail(err_msg=expire_msg) # Test SHA512 connection cur.execute("ALTER USER sha512_user IDENTIFIED BY 'password'") self.assertConnectionSuccess() # Switch to MD5 for hashing and storing the user password cur.execute("ALTER USER sha512_user SECURITY_ALGORITHM 'MD5'") self.assertConnectionFail(err_msg=expire_msg) # Test MD5 connection cur.execute("ALTER USER sha512_user IDENTIFIED BY 'password'") self.assertConnectionSuccess() finally: # Must clean up authentication methods within this session, no # matter whether an exception has occurred or not, otherwise # those authentication methods may affect existing users cur.execute("DROP USER IF EXISTS sha512_user") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv4hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv6hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testlocalHash CASCADE") def test_password_expire(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP USER IF EXISTS pw_expire_user") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv4hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv6hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testlocalHash CASCADE") try: cur.execute("CREATE USER pw_expire_user IDENTIFIED BY 'password'") cur.execute("CREATE AUTHENTICATION testIPv4hostHash METHOD 'hash' HOST '0.0.0.0/0'") cur.execute("GRANT AUTHENTICATION testIPv4hostHash TO pw_expire_user") cur.execute("CREATE AUTHENTICATION testIPv6hostHash METHOD 'hash' HOST '::/0'") cur.execute("GRANT AUTHENTICATION testIPv6hostHash TO pw_expire_user") cur.execute("CREATE AUTHENTICATION testlocalHash METHOD 'hash' LOCAL") cur.execute("GRANT AUTHENTICATION testlocalHash TO pw_expire_user") # Test connection self._conn_info['user'] = 'pw_expire_user' self._conn_info['password'] = 'password' self.assertConnectionSuccess() # Expire the user's password immediately cur.execute("ALTER USER pw_expire_user PASSWORD EXPIRE") # Expect an error message self.assertConnectionFail( err_msg='The password for user pw_expire_user has expired') finally: # Must clean up authentication methods within this session, no # matter whether an exception has occurred or not, otherwise # those authentication methods may affect existing users cur.execute("DROP USER IF EXISTS pw_expire_user") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv4hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testIPv6hostHash CASCADE") cur.execute("DROP AUTHENTICATION IF EXISTS testlocalHash CASCADE") exec(AuthenticationTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_cancel.py000066400000000000000000000041521357720411700273610ustar00rootroot00000000000000# Copyright (c) 2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import from multiprocessing import Process import pytest import time from .base import VerticaPythonIntegrationTestCase from ... import errors class CancelTestCase(VerticaPythonIntegrationTestCase): def test_cursor_cancel(self): # Cursor.cancel() should be not supported any more with self._connect() as conn: cursor = conn.cursor() with self.assertRaises(errors.NotSupportedError): cursor.cancel() def test_connection_cancel_no_query(self): with self._connect() as conn: cur = conn.cursor() # No query is being executed, cancel does nothing conn.cancel() @pytest.mark.timeout(30) def test_connection_cancel_running_query(self): def cancel_query(conn, delay=5): time.sleep(delay) conn.cancel() with self._connect() as conn: cur = conn.cursor() p1 = Process(target=cancel_query, args=(conn,)) p1.start() with self.assertRaises(errors.QueryCanceled): long_running_query = ('select count(*) from ' '(select node_name from CONFIGURATION_PARAMETERS) as a cross join ' '(select node_name from CONFIGURATION_PARAMETERS) as b cross join ' '(select node_name from CONFIGURATION_PARAMETERS) as c') cur.execute(long_running_query) p1.join() exec(CancelTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_column.py000066400000000000000000000046701357720411700274360ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .base import VerticaPythonIntegrationTestCase class ColumnTestCase(VerticaPythonIntegrationTestCase): def test_column_names_query(self): columns = ['isocode', 'name', u'\uFF04'] with self._connect() as conn: cur = conn.cursor() cur.execute(u""" SELECT 'US' AS {0}, 'United States' AS {1}, 'USD' AS {2} UNION ALL SELECT 'CA', 'Canada', 'CAD' UNION ALL SELECT 'MX', 'Mexico', 'MXN' """.format(*columns)) description = cur.description self.assertListEqual([d.name for d in description], columns) exec(ColumnTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_connection.py000066400000000000000000000076561357720411700303070ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import getpass import uuid from .base import VerticaPythonIntegrationTestCase class ConnectionTestCase(VerticaPythonIntegrationTestCase): def tearDown(self): super(ConnectionTestCase, self).tearDown() if 'session_label' in self._conn_info: del self._conn_info['session_label'] def test_client_os_user_name_metadata(self): value = getpass.getuser() # Metadata client_os_user_name sent from client should be captured into system tables query = 'SELECT client_os_user_name FROM v_monitor.current_session' res = self._query_and_fetchone(query) self.assertEqual(res[0], value) query = 'SELECT client_os_user_name FROM v_monitor.sessions WHERE session_id=(SELECT current_session())' res = self._query_and_fetchone(query) self.assertEqual(res[0], value) query = 'SELECT client_os_user_name FROM v_monitor.user_sessions WHERE session_id=(SELECT current_session())' res = self._query_and_fetchone(query) self.assertEqual(res[0], value) query = 'SELECT client_os_user_name FROM v_internal.dc_session_starts WHERE session_id=(SELECT current_session())' res = self._query_and_fetchone(query) self.assertEqual(res[0], value) def test_session_label(self): label = str(uuid.uuid1()) self._conn_info['session_label'] = label query = 'SELECT client_label FROM v_monitor.current_session' res = self._query_and_fetchone(query) self.assertEqual(res[0], label) query = 'SELECT client_label FROM v_monitor.sessions WHERE session_id=(SELECT current_session())' res = self._query_and_fetchone(query) self.assertEqual(res[0], label) query = 'SELECT client_label FROM v_monitor.user_sessions WHERE session_id=(SELECT current_session())' res = self._query_and_fetchone(query) self.assertEqual(res[0], label) query = 'SELECT client_label FROM v_internal.dc_session_starts WHERE session_id=(SELECT current_session())' res = self._query_and_fetchone(query) self.assertEqual(res[0], label) exec(ConnectionTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_cursor.py000066400000000000000000001266471357720411700274670ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from datetime import date, datetime, time from decimal import Decimal from uuid import UUID import logging import os as _os import pytest import re import tempfile from parameterized import parameterized from .base import VerticaPythonIntegrationTestCase from ... import errors """ There are a couple of testcases in this file, they are 1. CursorTestCase: general cursor tests, not sensitive to query protocols. 2. SimpleQueryTestCase: simple query protocol tests in execute(). 3. SimpleQueryExecutemanyTestCase: simple query protocol tests in executemany(). 4. PreparedStatementTestCase: prepared statements tests in both execute() and executemany(). Different query protocols use different paramstyles: - simple query protocol: 'named' and 'format' paramstyles - extended query protocol (prepared statements): 'qmark' paramstyle """ class CursorTestCase(VerticaPythonIntegrationTestCase): def setUp(self): super(CursorTestCase, self).setUp() self._table = 'cursor_test' self._init_table() def _init_table(self): with self._connect() as conn: cur = conn.cursor() # clean old table cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) # create test table cur.execute("""CREATE TABLE {0} ( a INT, b VARCHAR(32) ) """.format(self._table)) def tearDown(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) super(CursorTestCase, self).tearDown() def test_multi_inserts_and_transaction(self): with self._connect() as conn1, self._connect() as conn2: cur1 = conn1.cursor() cur2 = conn2.cursor() # insert data without a commit cur1.execute("INSERT INTO {0} (a, b) VALUES (2, 'bb')".format(self._table)) # verify we can see it from this cursor cur1.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table)) res_from_cur_1_before_commit = cur1.fetchall() self.assertListOfListsEqual(res_from_cur_1_before_commit, [[2, 'bb']]) # verify we cant see it from other cursor cur2.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table)) res_from_cur2_before_commit = cur2.fetchall() self.assertListOfListsEqual(res_from_cur2_before_commit, []) # insert more data then commit cur1.execute("INSERT INTO {0} (a, b) VALUES (3, 'cc')".format(self._table)) cur1.execute("COMMIT") # verify we can see it from this cursor cur1.execute( "SELECT a, b FROM {0} WHERE a = 2 OR a = 3 ORDER BY a".format(self._table)) res_from_cur1_after_commit = cur1.fetchall() self.assertListOfListsEqual(res_from_cur1_after_commit, [[2, 'bb'], [3, 'cc']]) # verify we can see it from other cursor cur2.execute( "SELECT a, b FROM {0} WHERE a = 2 OR a = 3 ORDER BY a".format(self._table)) res_from_cur2_after_commit = cur2.fetchall() self.assertListOfListsEqual(res_from_cur2_after_commit, [[2, 'bb'], [3, 'cc']]) def test_conn_commit(self): with self._connect() as conn: cur = conn.cursor() cur.execute("INSERT INTO {0} (a, b) VALUES (5, 'cc')".format(self._table)) conn.commit() with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT a, b FROM {0} WHERE a = 5".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[5, 'cc']]) def test_delete(self): with self._connect() as conn: cur = conn.cursor() cur.execute("INSERT INTO {0} (a, b) VALUES (5, 'cc')".format(self._table)) self.assertEqual(cur.rowcount, -1) update_res = cur.fetchall() self.assertListOfListsEqual(update_res, [[1]]) conn.commit() # validate delete count cur.execute("DELETE FROM {0} WHERE a = 5".format(self._table)) self.assertEqual(cur.rowcount, -1) delete_res = cur.fetchall() self.assertListOfListsEqual(delete_res, [[1]]) conn.commit() # validate deleted cur.execute("SELECT a, b FROM {0} WHERE a = 5".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, []) def test_update(self): with self._connect() as conn: cur = conn.cursor() cur.execute("INSERT INTO {0} (a, b) VALUES (5, 'cc')".format(self._table)) # validate insert count insert_res = cur.fetchall() self.assertListOfListsEqual(insert_res, [[1]], msg='Bad INSERT response') conn.commit() cur.execute("UPDATE {0} SET b = 'ff' WHERE a = 5".format(self._table)) # validate update count assert cur.rowcount == -1 update_res = cur.fetchall() self.assertListOfListsEqual(update_res, [[1]], msg='Bad UPDATE response') conn.commit() cur.execute("SELECT a, b FROM {0} WHERE a = 5".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[5, 'ff']]) def test_copy_null(self): with self._connect() as conn: cur = conn.cursor() cur.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), "1,\n,foo") cur.execute("SELECT a, b FROM {0} ORDER BY a ASC".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[None, 'foo'], [1, None]]) def test_copy_with_string(self): with self._connect() as conn1, self._connect() as conn2: cur1 = conn1.cursor() cur2 = conn2.cursor() cur1.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), "1,foo\n2,bar") # no commit necessary for copy cur1.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table)) res_from_cur1 = cur1.fetchall() self.assertListOfListsEqual(res_from_cur1, [[1, 'foo']]) cur2.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table)) res_from_cur2 = cur2.fetchall() self.assertListOfListsEqual(res_from_cur2, [[2, 'bar']]) # integration test for #345 def test_copy_with_file_like_object(self): class FileWrapper: read = property(lambda self: self.file.read) seek = property(lambda self: self.file.seek) write = property(lambda self: self.file.write) def __init__(self, file): self.file = file with tempfile.TemporaryFile() as f, self._connect() as conn: wrapped_f = FileWrapper(f) # object with a read() method wrapped_f.write(b"1,foo\n2,bar") # move rw pointer to top of file wrapped_f.seek(0) cur = conn.cursor() cur.copy( "COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), wrapped_f ) cur.execute("SELECT * FROM {0} ORDER BY a ASC".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[1, 'foo'], [2, 'bar']]) # integration test for #325 @parameterized.expand([ (tempfile.NamedTemporaryFile,), (tempfile.SpooledTemporaryFile,), (tempfile.TemporaryFile,), ]) def test_copy_with_temporary_file(self, temp_file_type): with temp_file_type() as f, self._connect() as conn: f.write(b"1,foo\n2,bar") f.seek(0) cur = conn.cursor() cur.copy( "COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), f, ) cur.execute("SELECT * FROM {0} ORDER BY a ASC".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[1, 'foo'], [2, 'bar']]) def test_copy_with_file(self): with tempfile.TemporaryFile() as f, self._connect() as conn1, self._connect() as conn2: f.write(b"1,foo\n2,bar") # move rw pointer to top of file f.seek(0) cur1 = conn1.cursor() cur2 = conn2.cursor() cur1.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), f) # no commit necessary for copy cur1.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table)) res_from_cur1 = cur1.fetchall() self.assertListOfListsEqual(res_from_cur1, [[1, 'foo']]) cur2.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table)) res_from_cur2 = cur2.fetchall() self.assertListOfListsEqual(res_from_cur2, [[2, 'bar']]) def test_copy_with_closed_file(self): with tempfile.TemporaryFile() as f, self._connect() as conn: f.write(b"1,foo\n2,bar") # move rw pointer to top of file f.seek(0) cur = conn.cursor() f.close() with pytest.raises(ValueError, match='closed file'): cur.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), f) cur.close() # unit test for #78 def test_copy_with_data_in_buffer(self): with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT 1;") res = cur.fetchall() self.assertListOfListsEqual(res, [[1]]) cur.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table), "1,foo\n2,bar") cur.execute("SELECT 1;") res = cur.fetchall() self.assertListOfListsEqual(res, [[1]]) # unit test for #213 def test_cmd_after_invalid_copy_stmt(self): with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT 1;") res = cur.fetchall() self.assertListOfListsEqual(res, [[1]]) res = [[]] try: cur.copy("COPY non_existing_tab(a, b) FROM STDIN DELIMITER ','", "FAIL") except errors.MissingRelation as e: cur.execute("SELECT 1;") res = cur.fetchall() self.assertListOfListsEqual(res, [[1]]) # unit test for #213 def test_cmd_after_rejected_copy_data(self): with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT 1;") res = cur.fetchall() self.assertListOfListsEqual(res, [[1]]) res = [[]] try: cur.copy("COPY {0} (a, b) FROM STDIN DELIMITER ',' ABORT ON ERROR".format(self._table), "FAIL") except errors.CopyRejected as e: cur.execute("SELECT 1;") res = cur.fetchall() self.assertListOfListsEqual(res, [[1]]) def test_with_conn(self): with self._connect() as conn: cur = conn.cursor() cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa');".format(self._table)) conn.commit() cur.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[1, 'aa']]) def test_iterator(self): with self._connect() as conn: cur = conn.cursor() values = [[1, 'aa'], [2, 'bb'], [3, 'cc']] for n, s in values: cur.execute("INSERT INTO {} (a, b) VALUES ({}, '{}')" .format(self._table, n, s)) conn.commit() cur.execute("SELECT a, b FROM {0} ORDER BY a ASC".format(self._table)) for val, res in zip(sorted(values), cur.iterate()): self.assertListEqual(res, val) remaining = cur.fetchall() self.assertListOfListsEqual(remaining, []) def test_mid_iterator_execution(self): with self._connect() as conn: cur = conn.cursor() values = [[1, 'aa'], [2, 'bb'], [3, 'cc']] for n, s in values: cur.execute("INSERT INTO {} (a, b) VALUES ({}, '{}')" .format(self._table, n, s)) conn.commit() cur.execute("SELECT a, b FROM {0} ORDER BY a ASC".format(self._table)) for val, res in zip(sorted(values), cur.iterate()): self.assertListEqual(res, val) break # stop after one comparison # make new query and verify result cur.execute("SELECT COUNT(*) FROM {0}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[3]]) def test_query_errors(self): with self._connect() as conn: cur = conn.cursor() # create table syntax error with self.assertRaises(errors.VerticaSyntaxError): cur.execute("""CREATE TABLE {0}_fail ( a INT, b VARCHAR(32),,, ); """.format(self._table)) # select table not found error cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa');".format(self._table)) cur.execute("COMMIT;") with self.assertRaises(errors.QueryError): cur.execute("SELECT * FROM {0}_fail".format(self._table)) # generate a user-defined error message err_msg = 'USER GENERATED ERROR: test error' with pytest.raises(errors.QueryError, match=err_msg): cur.execute("SELECT THROW_ERROR('test error')") # verify cursor still usable after errors cur.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[1, 'aa']]) def test_cursor_close_and_reuse(self): with self._connect() as conn: cur = conn.cursor() # insert data cur.execute("INSERT INTO {0} (a, b) VALUES (2, 'bb');".format(self._table)) cur.execute("COMMIT;") # (query -> close -> reopen) * 3 times for _ in range(3): cur.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[2, 'bb']]) # close and reopen cursor cur.close() with pytest.raises(errors.InterfaceError, match='Cursor is closed'): cur.execute("SELECT 1;") cur = conn.cursor() def test_format_quote_unicode(self): with self._connect() as conn: cur = conn.cursor() bad_word = u'Fr\xfchst\xfcck' formatted_word = u''.join((u'"', re.escape(bad_word), u'"')) self.assertEqual(formatted_word, cur.format_quote(bad_word, True)) def test_udtype(self): poly = "POLYGON ((1 2, 2 3, 3 1, 1 2))" line = "LINESTRING (42.1 71, 41.4 70, 41.3 72.9, 42.99 71.46, 44.47 73.21)" with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) cur.execute("CREATE TABLE {} (c1 GEOMETRY(10000), c2 GEOGRAPHY(1000))" .format(self._table)) cur.execute("INSERT INTO {} VALUES (ST_GeomFromText('{}'), ST_GeographyFromText('{}'))" .format(self._table, poly, line)) conn.commit() cur.execute("SELECT c1, c2, ST_AsText(c1), ST_AsText(c2) FROM {}".format(self._table)) res = cur.fetchall() self.assertEqual(res[0][2], poly) self.assertEqual(res[0][3], line) datatype_names = [col.type_name for col in cur.description] expected = ['geometry', 'geography', 'Long Varchar', 'Long Varchar'] self.assertListEqual(datatype_names, expected) self.assertEqual(cur.description[0].display_size, 10000) self.assertEqual(cur.description[1].display_size, 1000) exec(CursorTestCase.createPrepStmtClass()) class SimpleQueryTestCase(VerticaPythonIntegrationTestCase): @classmethod def setUpClass(cls): super(SimpleQueryTestCase, cls).setUpClass() cls._conn_info['use_prepared_statements'] = False def setUp(self): super(SimpleQueryTestCase, self).setUp() self._table = 'simplequery_test' with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) def tearDown(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) super(SimpleQueryTestCase, self).tearDown() def test_inline_commit(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {0} (a INT, b VARCHAR)".format(self._table)) cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa'); COMMIT;" .format(self._table)) cur.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table)) # unknown rowcount self.assertEqual(cur.rowcount, -1) res = cur.fetchall() self.assertEqual(cur.rowcount, 1) self.assertListOfListsEqual(res, [[1, 'aa']]) # unit test for #144 def test_empty_query(self): with self._connect() as conn: cur = conn.cursor() cur.execute("") res = cur.fetchall() self.assertListOfListsEqual(res, []) cur.execute("--select 1") res = cur.fetchall() self.assertListOfListsEqual(res, []) cur.execute(""" /* Test block comment */ """) res = cur.fetchall() self.assertListOfListsEqual(res, []) # unit test for #175 def test_datetime_types(self): with self._connect() as conn: cur = conn.cursor() # create test table cur.execute("""CREATE TABLE {0} ( a INT, b VARCHAR(32), c TIMESTAMP, d DATE, e TIME ) """.format(self._table)) cur.execute("INSERT INTO {0} VALUES (:n, :s, :dt, :d, :t)".format(self._table), {'n': 10, 's': 'aa', 'dt': datetime(2018, 9, 7, 15, 38, 19, 769000), 'd': date(2018, 9, 7), 't': time(13, 50, 9)}) conn.commit() cur.execute("SELECT a, b, c, d, e FROM {0}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[10, 'aa', datetime(2018, 9, 7, 15, 38, 19, 769000), date(2018, 9, 7), time(13, 50, 9)]]) def test_binary_types(self): with self._connect() as conn: cur = conn.cursor() # create test table cur.execute("""CREATE TABLE {0} ( a binary(1), b binary(3), c varbinary ) """.format(self._table)) cur.execute("INSERT INTO {0} VALUES (:b, :s1, :s2)".format(self._table), {'b': b'x', 's1': b'xyz', 's2': 'abcde'}) conn.commit() cur.execute("SELECT a, b, c FROM {0}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[b'x', b'xyz', b'abcde']]) def test_uuid_type(self): with self._connect() as conn: cur = conn.cursor() x = UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}') y = UUID('00000020-0000-0000-0000-100000000000') z = UUID('00100010000000000000000000000000') # create test table cur.execute("CREATE TABLE {0} ( a uuid, b uuid, c uuid )".format(self._table)) cur.execute("INSERT INTO {0} VALUES (:u1, :u2, :u3)".format(self._table), {'u1': x, 'u2': y, 'u3': z}) conn.commit() cur.execute("SELECT a, b, c FROM {0}".format(self._table)) res = cur.fetchall()[0] self.assertListEqual([str(i) for i in res], [str(x), str(y), str(z)]) # unit test for #74 def test_nextset(self): with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT 1; SELECT 'foo';") res1 = cur.fetchall() self.assertListOfListsEqual(res1, [[1]]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) res2 = cur.fetchall() self.assertListOfListsEqual(res2, [['foo']]) self.assertIsNone(cur.fetchone()) self.assertFalse(cur.nextset()) # unit test for #74 def test_nextset_with_delete(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {0} (a INT, b VARCHAR(32))".format(self._table)) # insert data cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa')".format(self._table)) cur.execute("INSERT INTO {0} (a, b) VALUES (2, 'bb')".format(self._table)) conn.commit() cur.execute(""" SELECT * FROM {0} ORDER BY a ASC; DELETE FROM {0}; SELECT * FROM {0} ORDER BY a ASC; """.format(self._table)) # check first select results res1 = cur.fetchall() self.assertListOfListsEqual(res1, [[1, 'aa'], [2, 'bb']]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) # check delete results res2 = cur.fetchall() self.assertListOfListsEqual(res2, [[2]]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) # check second select results res3 = cur.fetchall() self.assertListOfListsEqual(res3, []) self.assertIsNone(cur.fetchone()) self.assertFalse(cur.nextset()) # unit test for #124 def test_nextset_with_error(self): with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT 1; SELECT a; SELECT 2") # verify data from first query res1 = cur.fetchall() self.assertListOfListsEqual(res1, [[1]]) self.assertIsNone(cur.fetchone()) # second statement results in a query error with self.assertRaises(errors.MissingColumn): cur.nextset() def test_qmark_paramstyle(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {0} (a INT, b VARCHAR)".format(self._table)) err_msg = 'not all arguments converted during string formatting' values = [1, 'aa'] with pytest.raises(TypeError, match=err_msg): cur.execute("INSERT INTO {} VALUES (?, ?)".format(self._table), values) cur.execute("INSERT INTO {} VALUES (?, ?)".format(self._table), values, use_prepared_statements=True) cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [values]) def test_execute_parameters(self): with self._connect() as conn: cur = conn.cursor() all_chars = u"".join(chr(i) for i in range(1, 128)) backslash_data = u"\\backslash\\ \\data\\\\" cur.execute("SELECT :a, :b", parameters={"a": all_chars, "b": backslash_data}) self.assertEqual([all_chars, backslash_data], cur.fetchone()) def test_execute_percent_parameters(self): with self._connect() as conn: cur = conn.cursor() all_chars = u"".join(chr(i) for i in range(1, 128)) backslash_data = u"\\backslash\\ \\data\\\\" cur.execute("SELECT %s, %s", parameters=[all_chars, backslash_data]) self.assertEqual([all_chars, backslash_data], cur.fetchone()) class SimpleQueryExecutemanyTestCase(VerticaPythonIntegrationTestCase): def setUp(self): super(SimpleQueryExecutemanyTestCase, self).setUp() self._table = 'executemany_test' self._init_table() def _init_table(self): with self._connect() as conn: cur = conn.cursor() # clean old table cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) # create test table cur.execute("""CREATE TABLE {0} ( a INT, b VARCHAR(32) ) """.format(self._table)) def tearDown(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) super(SimpleQueryExecutemanyTestCase, self).tearDown() def _test_executemany(self, table, seq_of_values): with self._connect() as conn: cur = conn.cursor() cur.executemany("INSERT INTO {0} (a, b) VALUES (%s, %s)".format(table), seq_of_values) conn.commit() cur.execute("SELECT * FROM {0} ORDER BY a ASC, b ASC".format(table)) # check first select results res1 = cur.fetchall() seq_of_values_to_compare = sorted([list(values) for values in seq_of_values]) self.assertListOfListsEqual(res1, seq_of_values_to_compare) self.assertIsNone(cur.fetchone()) def test_executemany(self): self._test_executemany(self._table, [(1, 'aa'), (2, 'bb')]) def test_executemany_quoted_path(self): table = '.'.join(['"{}"'.format(s.strip('"')) for s in self._table.split('.')]) self._test_executemany(table, [(1, 'aa'), (2, 'bb')]) def test_executemany_utf8(self): self._test_executemany(self._table, [(1, u'a\xfc'), (2, u'bb')]) # test for #292 def test_executemany_autocommit(self): with self._connect() as conn: cur = conn.cursor() cur.execute('SET SESSION AUTOCOMMIT TO off') cur.execute('BEGIN') cur.executemany("INSERT INTO {0} (a, b) VALUES (%s, %s)".format(self._table), ((None, 'foo'), [2, None], [3, 'bar'])) cur.execute('ROLLBACK') cur.execute("SELECT count(*) FROM {0}".format(self._table)) res = cur.fetchone()[0] self.assertEqual(res, 0) def test_executemany_null(self): seq_of_values_1 = ((None, 'foo'), [2, None]) seq_of_values_2 = ({'a': None, 'b': 'bar'}, {'a': 4, 'b': None}) seq_of_values_to_compare = [[None, 'bar'], [None, 'foo'], [2, None], [4, None]] with self._connect() as conn: cur = conn.cursor() cur.executemany("INSERT INTO {0} (a, b) VALUES (%s, %s)".format(self._table), seq_of_values_1) conn.commit() cur.executemany("INSERT INTO {0} (a, b) VALUES (:a, :b)".format(self._table), seq_of_values_2) conn.commit() cur.execute("SELECT * FROM {0} ORDER BY a ASC, b ASC".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, seq_of_values_to_compare) self.assertIsNone(cur.fetchone()) def test_executemany_empty(self): err_msg = "executemany is implemented for simple INSERT statements only" with self._connect() as conn: cur = conn.cursor() with pytest.raises(NotImplementedError, match=err_msg): cur.executemany("", []) class PreparedStatementTestCase(VerticaPythonIntegrationTestCase): @classmethod def setUpClass(cls): super(PreparedStatementTestCase, cls).setUpClass() cls._conn_info['use_prepared_statements'] = True def setUp(self): super(PreparedStatementTestCase, self).setUp() self._table = 'preparedstmt_test' with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) def tearDown(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS {0}".format(self._table)) super(PreparedStatementTestCase, self).tearDown() def test_empty_statement(self): with self._connect() as conn: cur = conn.cursor() err_msg = 'The statement being prepared is empty' with pytest.raises(errors.EmptyQueryError, match=err_msg): cur.execute("") with pytest.raises(errors.EmptyQueryError, match=err_msg): cur.executemany("", [()]) with pytest.raises(errors.EmptyQueryError, match=err_msg): cur.execute("--select 1") with pytest.raises(errors.EmptyQueryError, match=err_msg): cur.execute(""" /* Test block comment */ """) def test_compound_statement(self): with self._connect() as conn: cur = conn.cursor() query = "select 1; select 2; select 3;" err_msg = 'Cannot insert multiple commands into a prepared statement' with pytest.raises(errors.VerticaSyntaxError, match=err_msg): cur.execute(query) def test_num_of_parameters(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {} (a int, b varchar)".format(self._table)) err_msg = 'Invalid number of parameters' # The number of parameters is less than columns of table with pytest.raises(ValueError, match=err_msg): cur.execute("INSERT INTO {} VALUES (?,?)".format(self._table)) # The number of parameters is greater than columns of table with pytest.raises(ValueError, match=err_msg): cur.execute("INSERT INTO {} VALUES (?,?)".format(self._table), [1, 'foo', 2]) # The number of parameters is equal to columns of table values = [1, 'varchar'] cur.execute("INSERT INTO {} VALUES (?,?)".format(self._table), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [values]) def test_format_paramstyle(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {} (a int, b varchar)".format(self._table)) err_msg = 'Syntax error at or near "%"' values = [1, 'varchar'] with pytest.raises(errors.VerticaSyntaxError, match=err_msg): cur.execute("INSERT INTO {} VALUES (%s, %s)".format(self._table), values) cur.execute("INSERT INTO {} VALUES (%s, %s)".format(self._table), values, use_prepared_statements=False) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [values]) def test_named_paramstyle(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {} (a int, b varchar)".format(self._table)) err_msg = 'Execute parameters should be a list/tuple' values = {'a': 1, 'b': 'varchar'} with pytest.raises(TypeError, match=err_msg): cur.execute("INSERT INTO {} VALUES (:a, :b)".format(self._table), values) cur.execute("INSERT INTO {} VALUES (:a, :b)".format(self._table), values, use_prepared_statements=False) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [[1, 'varchar']]) def test_executemany(self): values = ((None, 'foo'), [1, 'aa'], (2, None), [2, u'a\xfc']) expected = [[None, 'foo'], [1, 'aa'], [2, None], [2, u'a\xfc']] with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {} (a int, b varchar)".format(self._table)) cur.executemany("INSERT INTO {} VALUES (?, ?)".format(self._table), values) self.assertListOfListsEqual(cur.fetchall(), [[1]]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) self.assertListOfListsEqual(cur.fetchall(), [[1]]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) self.assertListOfListsEqual(cur.fetchall(), [[1]]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) self.assertListOfListsEqual(cur.fetchall(), [[1]]) self.assertIsNone(cur.fetchone()) self.assertFalse(cur.nextset()) cur.executemany("SELECT * FROM {} WHERE a=? ORDER BY b" .format(self._table), [[1], [2], [3]]) self.assertListOfListsEqual(cur.fetchall(), [[1, 'aa']]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) self.assertListOfListsEqual(cur.fetchall(), [[2, u'a\xfc'], [2, None]]) self.assertIsNone(cur.fetchone()) self.assertTrue(cur.nextset()) self.assertListOfListsEqual(cur.fetchall(), []) self.assertIsNone(cur.fetchone()) self.assertFalse(cur.nextset()) def test_bind_boolean(self): values = (True, 't', 'true', '1', 1, 'Yes', 'y', None, False, 'f', 'false', '0', 0, 'No', 'n') expected = [[True] * 7 + [None] + [False] * 7] with self._connect() as conn: cur = conn.cursor() cur.execute("""CREATE TABLE {} ( col_1 BOOL, col_2 BOOL, col_3 BOOL, col_4 BOOL, col_5 BOOL, col_6 BOOL, col_7 BOOL, col_8 BOOL, col_9 BOOL, col_10 BOOL, col_11 BOOL, col_12 BOOL, col_13 BOOL, col_14 BOOL, col_15 BOOL )""".format(self._table)) cur.execute("INSERT INTO {} VALUES ({})".format( self._table, ','.join(['?']*15)), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, expected) def test_bind_datetime(self): with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {} (a TIMESTAMP, b DATE, c TIME, " "d TIMESTAMP, e DATE, f TIME)".format(self._table)) values = [datetime(2018, 9, 7, 15, 38, 19, 769000), date(2018, 9, 7), time(13, 50, 9), None, None, None] cur.execute("INSERT INTO {} VALUES (?,?,?,?,?,?)".format(self._table), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, [values]) def test_bind_binary(self): values = [b'binary data', b'\\backslash data\\', u'\\backslash data\\', u'\u00f1 encoding', 'raw data', 'long varbinary data', None] expected = [[b'binary data\\000\\000\\000', b'\\\\backslash data\\\\', b'\\\\backslash data\\\\', b'\\303\\261 encoding', b'raw data', b'long varbinary data', None]] with self._connect() as conn: cur = conn.cursor() cur.execute("""CREATE TABLE {} ( col_1 BINARY(14), col_2 VARBINARY, col_3 VARBINARY, col_4 BYTEA, col_5 RAW, col_6 LONG VARBINARY, col_7 VARBINARY)""".format(self._table)) cur.execute("INSERT INTO {} VALUES ({})".format( self._table, ','.join(['?']*7)), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, expected) def test_bind_numeric(self): values = [Decimal('123456789.98765'), Decimal('123456789.98765'), Decimal('123456789.98765'), Decimal('123456789.98765'), 10, 11, 1234567890, 1296554905964, 123, 123, 123.45, 123.45, 123.45, 123.45, 123.45, None, None, None] expected = [[Decimal('123456789.9877'), Decimal('123456789.987650000000000'), Decimal('123456790'), Decimal('123456789.987650000000000'), 10, 11, 1234567890, 1296554905964, 123, 123, 123.45, 123.45, 123.45, 123.45, 123.45, None, None, None]] with self._connect() as conn: cur = conn.cursor() cur.execute("""CREATE TABLE {} ( col_1 MONEY, col_2 NUMERIC, col_3 NUMBER, col_4 DECIMAL, col_5 TINYINT, col_6 SMALLINT, col_7 INT8, col_8 BIGINT, col_9 INT, col_10 INTEGER, col_11 DOUBLE PRECISION, col_12 REAL, col_13 FLOAT8, col_14 FLOAT(10), col_15 FLOAT, col_16 NUMERIC, col_17 INT, col_18 FLOAT )""".format(self._table)) cur.execute("INSERT INTO {} VALUES ({})".format( self._table, ','.join(['?']*18)), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, expected) def test_bind_character(self): values = ['char data', b'raw varchar data', u'long varbinary data \u00f1', None, None, None] expected = [['char data ', 'raw varchar data', u'long varbinary data \u00f1', None, None, None]] with self._connect() as conn: cur = conn.cursor() cur.execute("""CREATE TABLE {} ( col_1 CHAR(10), col_2 VARCHAR, col_3 LONG VARCHAR, col_4 CHAR(10), col_5 VARCHAR, col_6 LONG VARCHAR )""".format(self._table)) cur.execute("INSERT INTO {} VALUES ({})".format( self._table, ','.join(['?']*6)), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, expected) def test_bind_interval(self): values = ['1y 10m', '1 2', '1y 10m', '1y 10m', '17910y 1h 3m 6s 5msecs 57us ago', '3 days 2 hours', '1 3', '1y 15 mins 20 sec', '15 mins 20 sec', '1y 5 mins 20 sec', '2 days 12 hours 15 mins 1235 milliseconds', '2 days 12 hours 15 mins ago', '2 days 12 hours 15 mins ago', None, None] expected = [[b'1', b'1-2', b'22', b'365', b'-6537150 01:03:06.0051', b'74', b'01:03', b'8760:15:20', b'15', b'525605:20', b'216901.24', b'-2 12', b'-2 12:15', None, None]] with self._connect() as conn: cur = conn.cursor() cur.execute("""CREATE TABLE {} ( col_1 INTERVAL YEAR, col_2 INTERVAL YEAR TO MONTH, col_3 INTERVAL MONTH, col_4 INTERVAL DAY, col_5 INTERVAL DAY TO SECOND(4), col_6 INTERVAL HOUR, col_7 INTERVAL HOUR to MINUTE, col_8 INTERVAL HOUR to SECOND, col_9 INTERVAL MINUTE, col_10 INTERVAL MINUTE TO SECOND, col_11 INTERVAL SECOND(2), col_12 INTERVAL DAY TO HOUR, col_13 INTERVAL DAY TO MINUTE, col_14 INTERVAL YEAR TO MONTH, col_15 INTERVAL DAY TO SECOND )""".format(self._table)) cur.execute("INSERT INTO {} VALUES ({})".format( self._table, ','.join(['?']*15)), values) conn.commit() cur.execute("SELECT * FROM {}".format(self._table)) res = cur.fetchall() self.assertListOfListsEqual(res, expected) def test_bind_udtype(self): poly = "POLYGON ((1 2, 2 3, 3 1, 1 2))" line = "LINESTRING (42.1 71, 41.4 70, 41.3 72.9, 42.99 71.46, 44.47 73.21)" with self._connect() as conn: cur = conn.cursor() cur.execute("CREATE TABLE {} (c1 GEOMETRY(10000), c2 GEOGRAPHY(1000))" .format(self._table)) cur.execute("INSERT INTO {} VALUES (ST_GeomFromText(?), ST_GeographyFromText(?))" .format(self._table), [poly, line]) conn.commit() cur.execute("SELECT c1, c2, ST_AsText(c1), ST_AsText(c2) FROM {}".format(self._table)) res = cur.fetchall() self.assertEqual(res[0][2], poly) self.assertEqual(res[0][3], line) datatype_names = [col.type_name for col in cur.description] expected = ['geometry', 'geography', 'Long Varchar', 'Long Varchar'] self.assertListEqual(datatype_names, expected) self.assertEqual(cur.description[0].display_size, 10000) self.assertEqual(cur.description[1].display_size, 1000) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_datatypes.py000066400000000000000000000055521357720411700301370ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from decimal import Decimal from uuid import UUID from .base import VerticaPythonIntegrationTestCase class TypeTestCase(VerticaPythonIntegrationTestCase): def test_decimal_query(self): value = Decimal(0.42) query = "SELECT {0}::numeric".format(value) res = self._query_and_fetchone(query) self.assertAlmostEqual(res[0], value) def test_boolean_query__true(self): value = True query = "SELECT {0}::boolean".format(value) res = self._query_and_fetchone(query) self.assertEqual(res[0], value) def test_boolean_query__false(self): value = False query = "SELECT {0}::boolean".format(value) res = self._query_and_fetchone(query) self.assertEqual(res[0], value) def test_uuid_query(self): self.require_protocol_at_least(3 << 16 | 8) value = UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') query = "SELECT '{0}'::uuid".format(value) res = self._query_and_fetchone(query) self.assertEqual(res[0], value) exec(TypeTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_dates.py000066400000000000000000000165771357720411700272520ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from collections import namedtuple from datetime import date from .base import VerticaPythonIntegrationTestCase from ... import errors DateTestingCase = namedtuple("DateTestingCase", ["string", "template", "date"]) class DateParsingTestCase(VerticaPythonIntegrationTestCase): """Testing DATE type parsing with focus on 'AD'/'BC'. Note: the 'BC' or 'AD' era indicators in Vertica's date format seem to make Vertica behave as follows: 1. Both 'BC' and 'AD' are simply a flags that tell Vertica: include era indicator if the date is Before Christ 2. Dates in 'AD' will never include era indicator """ def _test_dates(self, test_cases, msg=None): with self._connect() as conn: cur = conn.cursor() for tc in test_cases: cur.execute("SELECT TO_DATE('{0}', '{1}')".format(tc.string, tc.template)) res = cur.fetchall() self.assertListOfListsEqual(res, [[tc.date]], msg=msg) def _test_not_supported(self, test_cases, msg=None): with self._connect() as conn: cur = conn.cursor() for tc in test_cases: with self.assertRaises(errors.NotSupportedError, msg=msg): cur.execute("SELECT TO_DATE('{0}', '{1}')".format(tc.string, tc.template)) res = cur.fetchall() self.assertListOfListsEqual(res, [[tc.date]]) def test_no_to_no(self): test_cases = [ DateTestingCase('1985-10-25', 'YYYY-MM-DD', date(1985, 10, 25)), DateTestingCase('1955-11-12', 'YYYY-MM-DD', date(1955, 11, 12)), DateTestingCase('1885-01-01', 'YYYY-MM-DD', date(1885, 1, 1)), DateTestingCase('2015-10-21', 'YYYY-MM-DD', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='no indicator -> no indicator') def test_ad_to_no(self): test_cases = [ DateTestingCase('1985-10-25 AD', 'YYYY-MM-DD', date(1985, 10, 25)), DateTestingCase('1955-11-12 AD', 'YYYY-MM-DD', date(1955, 11, 12)), DateTestingCase('1885-01-01 AD', 'YYYY-MM-DD', date(1885, 1, 1)), DateTestingCase('2015-10-21 AD', 'YYYY-MM-DD', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='AD indicator -> no indicator') def test_bc_to_no(self): test_cases = [ DateTestingCase('1985-10-25 BC', 'YYYY-MM-DD', date(1985, 10, 25)), DateTestingCase('1955-11-12 BC', 'YYYY-MM-DD', date(1955, 11, 12)), DateTestingCase('1885-01-01 BC', 'YYYY-MM-DD', date(1885, 1, 1)), DateTestingCase('2015-10-21 BC', 'YYYY-MM-DD', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='BC indicator -> no indicator') def test_no_to_ad(self): test_cases = [ DateTestingCase('1985-10-25', 'YYYY-MM-DD AD', date(1985, 10, 25)), DateTestingCase('1955-11-12', 'YYYY-MM-DD AD', date(1955, 11, 12)), DateTestingCase('1885-01-01', 'YYYY-MM-DD AD', date(1885, 1, 1)), DateTestingCase('2015-10-21', 'YYYY-MM-DD AD', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='no indicator -> AD indicator') def test_ad_to_ad(self): test_cases = [ DateTestingCase('1985-10-25 AD', 'YYYY-MM-DD AD', date(1985, 10, 25)), DateTestingCase('1955-11-12 AD', 'YYYY-MM-DD AD', date(1955, 11, 12)), DateTestingCase('1885-01-01 AD', 'YYYY-MM-DD AD', date(1885, 1, 1)), DateTestingCase('2015-10-21 AD', 'YYYY-MM-DD AD', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='AD indicator -> AD indicator') def test_bc_to_ad(self): test_cases = [ DateTestingCase('1985-10-25 BC', 'YYYY-MM-DD AD', date(1985, 10, 25)), DateTestingCase('1955-11-12 BC', 'YYYY-MM-DD AD', date(1955, 11, 12)), DateTestingCase('1885-01-01 BC', 'YYYY-MM-DD AD', date(1885, 1, 1)), DateTestingCase('2015-10-21 BC', 'YYYY-MM-DD AD', date(2015, 10, 21)), ] self._test_not_supported(test_cases=test_cases, msg='BC indicator -> AD indicator') def test_no_to_bc(self): test_cases = [ DateTestingCase('1985-10-25', 'YYYY-MM-DD BC', date(1985, 10, 25)), DateTestingCase('1955-11-12', 'YYYY-MM-DD BC', date(1955, 11, 12)), DateTestingCase('1885-01-01', 'YYYY-MM-DD BC', date(1885, 1, 1)), DateTestingCase('2015-10-21', 'YYYY-MM-DD BC', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='no indicator -> BC indicator') def test_ad_to_bc(self): test_cases = [ DateTestingCase('1985-10-25 AD', 'YYYY-MM-DD BC', date(1985, 10, 25)), DateTestingCase('1955-11-12 AD', 'YYYY-MM-DD BC', date(1955, 11, 12)), DateTestingCase('1885-01-01 AD', 'YYYY-MM-DD BC', date(1885, 1, 1)), DateTestingCase('2015-10-21 AD', 'YYYY-MM-DD BC', date(2015, 10, 21)), ] self._test_dates(test_cases=test_cases, msg='AD indicator -> BC indicator') def test_bc_to_bc(self): test_cases = [ DateTestingCase('1985-10-25 BC', 'YYYY-MM-DD BC', date(1985, 10, 25)), DateTestingCase('1955-11-12 BC', 'YYYY-MM-DD BC', date(1955, 11, 12)), DateTestingCase('1885-01-01 BC', 'YYYY-MM-DD BC', date(1885, 1, 1)), DateTestingCase('2015-10-21 BC', 'YYYY-MM-DD BC', date(2015, 10, 21)), ] self._test_not_supported(test_cases=test_cases, msg='BC indicator -> BC indicator') exec(DateParsingTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_errors.py000066400000000000000000000055061357720411700274540ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .base import VerticaPythonIntegrationTestCase from ... import errors class ErrorTestCase(VerticaPythonIntegrationTestCase): def test_missing_schema(self): with self._connect() as conn: cur = conn.cursor() with self.assertRaises(errors.MissingSchema): cur.execute("SELECT 1 FROM missing_schema.table") def test_missing_relation(self): with self._connect() as conn: cur = conn.cursor() with self.assertRaises(errors.MissingRelation): cur.execute("SELECT 1 FROM missing_table") def test_duplicate_object(self): with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS duplicate_table") query = "CREATE TABLE duplicate_table (a BOOLEAN)" cur.execute(query) with self.assertRaises(errors.DuplicateObject): cur.execute(query) cur.execute("DROP TABLE IF EXISTS duplicate_table") exec(ErrorTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_loadbalance.py000066400000000000000000000316521357720411700303660ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .base import VerticaPythonIntegrationTestCase class LoadBalanceTestCase(VerticaPythonIntegrationTestCase): def setUp(self): super(LoadBalanceTestCase, self).setUp() self._host = self._conn_info['host'] self._port = self._conn_info['port'] def tearDown(self): self._conn_info['host'] = self._host self._conn_info['port'] = self._port self._conn_info['connection_load_balance'] = False self._conn_info['backup_server_node'] = [] with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT set_load_balance_policy('NONE')") cur.execute("DROP TABLE IF EXISTS test_loadbalance") super(LoadBalanceTestCase, self).tearDown() def get_node_num(self): with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT count(*) FROM nodes WHERE node_state='UP'") db_node_num = cur.fetchone()[0] return db_node_num def test_loadbalance_option_disabled(self): if 'connection_load_balance' in self._conn_info: del self._conn_info['connection_load_balance'] self.assertConnectionSuccess() self._conn_info['connection_load_balance'] = False self.assertConnectionSuccess() def test_loadbalance_random(self): self.require_DB_nodes_at_least(3) self._conn_info['connection_load_balance'] = True rowsToInsert = 3 * self.db_node_num with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT set_load_balance_policy('RANDOM')") cur.execute("DROP TABLE IF EXISTS test_loadbalance") cur.execute("CREATE TABLE test_loadbalance (n varchar)") # record which node the client has connected to for i in range(rowsToInsert): with self._connect() as conn1: cur1 = conn1.cursor() cur1.execute("INSERT INTO test_loadbalance (SELECT node_name FROM sessions " "WHERE session_id = (SELECT current_session()))") conn1.commit() cur.execute("SELECT count(DISTINCT n)>1 FROM test_loadbalance") res = cur.fetchone() self.assertTrue(res[0]) def test_loadbalance_none(self): # Client turns on connection_load_balance but server is unsupported with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT set_load_balance_policy('NONE')") self._conn_info['connection_load_balance'] = True # Client will proceed with the existing connection with initiator self.assertConnectionSuccess() # Test for multi-node DB self.require_DB_nodes_at_least(3) rowsToInsert = 3 * self.db_node_num with self._connect() as conn: cur = conn.cursor() cur.execute("DROP TABLE IF EXISTS test_loadbalance") cur.execute("CREATE TABLE test_loadbalance (n varchar)") # record which node the client has connected to for i in range(rowsToInsert): with self._connect() as conn1: cur1 = conn1.cursor() cur1.execute("INSERT INTO test_loadbalance (SELECT node_name FROM sessions " "WHERE session_id = (SELECT current_session()))") conn1.commit() cur.execute("SELECT count(DISTINCT n)=1 FROM test_loadbalance") res = cur.fetchone() self.assertTrue(res[0]) def test_loadbalance_roundrobin(self): self.require_DB_nodes_at_least(3) self._conn_info['connection_load_balance'] = True rowsToInsert = 3 * self.db_node_num with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT set_load_balance_policy('ROUNDROBIN')") cur.execute("DROP TABLE IF EXISTS test_loadbalance") cur.execute("CREATE TABLE test_loadbalance (n varchar)") # record which node the client has connected to for i in range(rowsToInsert): with self._connect() as conn1: cur1 = conn1.cursor() cur1.execute("INSERT INTO test_loadbalance (SELECT node_name FROM sessions " "WHERE session_id = (SELECT current_session()))") conn1.commit() cur.execute("SELECT count(n)=3 FROM test_loadbalance GROUP BY n") res = cur.fetchall() # verify that all db_node_num nodes are represented equally self.assertEqual(len(res), self.db_node_num) for i in res: self.assertEqual(i, [True]) def test_failover_empty_backup(self): # Connect to primary server if 'backup_server_node' in self._conn_info: del self._conn_info['backup_server_node'] self.assertConnectionSuccess() self._conn_info['backup_server_node'] = [] self.assertConnectionSuccess() # Set primary server to invalid host and port self._conn_info['host'] = 'invalidhost' self._conn_info['port'] = 9999 # Fail to connect to primary server self.assertConnectionFail() def test_failover_one_backup(self): # Set primary server to invalid host and port self._conn_info['host'] = 'invalidhost' self._conn_info['port'] = 9999 # One valid address in backup_server_node: port is an integer self._conn_info['backup_server_node'] = [(self._host, self._port)] self.assertConnectionSuccess() # One valid address in backup_server_node: port is a string self._conn_info['backup_server_node'] = [(self._host, str(self._port))] self.assertConnectionSuccess() # One invalid address in backup_server_node: DNS failed, Name or service not known self._conn_info['backup_server_node'] = [('invalidhost2', 8888)] self.assertConnectionFail() # One invalid address in backup_server_node: DNS failed, Name or service not known self._conn_info['backup_server_node'] = [('123.456.789.123', 8888)] self.assertConnectionFail() # One invalid address in backup_server_node: DNS failed, Address family for hostname not supported self._conn_info['backup_server_node'] = [('fd76:6572:7469:6361:0:242:ac11:4', 8888)] self.assertConnectionFail() # One invalid address in backup_server_node: Wrong port, connection refused self._conn_info['backup_server_node'] = [(self._host, 8888)] self.assertConnectionFail() def test_failover_multi_backup(self): # Set primary server to invalid host and port self._conn_info['host'] = 'invalidhost' self._conn_info['port'] = 9999 # One valid and two invalid addresses in backup_server_node self._conn_info['backup_server_node'] = [(self._host, self._port), 'invalidhost2', 'foo'] self.assertConnectionSuccess() self._conn_info['backup_server_node'] = ['foo', (self._host, self._port), ('123.456.789.1', 888)] self.assertConnectionSuccess() self._conn_info['backup_server_node'] = ['foo', ('invalidhost2', 8888), (self._host, self._port)] self.assertConnectionSuccess() # Three invalid addresses in backup_server_node self._conn_info['backup_server_node'] = ['foo', (self._host, 9999), ('123.456.789.1', 888)] self.assertConnectionFail() def test_failover_backup_format(self): # Set primary server to invalid host and port self._conn_info['host'] = 'invalidhost' self._conn_info['port'] = 9999 err_msg = 'Connection option "backup_server_node" must be a list' self._conn_info['backup_server_node'] = (self._host, self._port) self.assertConnectionFail(TypeError, err_msg) err_msg = ('Each item of connection option "backup_server_node"' r' must be a host string or a \(host, port\) tuple') self._conn_info['backup_server_node'] = [9999] self.assertConnectionFail(TypeError, err_msg) self._conn_info['backup_server_node'] = [(self._host, self._port, 'foo', 9999)] self.assertConnectionFail(TypeError, err_msg) err_msg = 'Host must be a string: invalid value: .*' self._conn_info['backup_server_node'] = [(9999, self._port)] self.assertConnectionFail(TypeError, err_msg) self._conn_info['backup_server_node'] = [(9999, 'port_num')] self.assertConnectionFail(TypeError, err_msg) err_msg = 'Port must be an integer or a string: invalid value: .*' self._conn_info['backup_server_node'] = [(self._host, 5433.0022)] self.assertConnectionFail(TypeError, err_msg) err_msg = r'Port .* is not a valid string: invalid literal for int\(\) with base 10: .*' self._conn_info['backup_server_node'] = [(self._host, 'port_num')] self.assertConnectionFail(ValueError, err_msg) self._conn_info['backup_server_node'] = [(self._host, '5433.0022')] self.assertConnectionFail(ValueError, err_msg) err_msg = 'Invalid port number: .*' self._conn_info['backup_server_node'] = [(self._host, -1000)] self.assertConnectionFail(ValueError, err_msg) self._conn_info['backup_server_node'] = [(self._host, 66000)] self.assertConnectionFail(ValueError, err_msg) self._conn_info['backup_server_node'] = [(self._host, '-1000')] self.assertConnectionFail(ValueError, err_msg) self._conn_info['backup_server_node'] = [(self._host, '66000')] self.assertConnectionFail(ValueError, err_msg) def test_failover_with_loadbalance_roundrobin(self): self.require_DB_nodes_at_least(3) # Set primary server to invalid host and port self._conn_info['host'] = 'invalidhost' self._conn_info['port'] = 9999 self.assertConnectionFail() self._conn_info['backup_server_node'] = [('invalidhost2', 8888), (self._host, self._port)] self.assertConnectionSuccess() self._conn_info['connection_load_balance'] = True rowsToInsert = 3 * self.db_node_num with self._connect() as conn: cur = conn.cursor() cur.execute("SELECT set_load_balance_policy('ROUNDROBIN')") cur.execute("DROP TABLE IF EXISTS test_loadbalance") cur.execute("CREATE TABLE test_loadbalance (n varchar)") # record which node the client has connected to for i in range(rowsToInsert): with self._connect() as conn1: cur1 = conn1.cursor() cur1.execute("INSERT INTO test_loadbalance (" "SELECT node_name FROM sessions " "WHERE session_id = (SELECT current_session()))") conn1.commit() cur.execute("SELECT count(n)=3 FROM test_loadbalance GROUP BY n") res = cur.fetchall() # verify that all db_node_num nodes are represented equally self.assertEqual(len(res), self.db_node_num) for i in res: self.assertEqual(i, [True]) exec(LoadBalanceTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_timezones.py000066400000000000000000000074361357720411700301610ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from collections import namedtuple from datetime import datetime from dateutil import tz from .base import VerticaPythonIntegrationTestCase TimeZoneTestingCase = namedtuple("TimeZoneTestingCase", ["string", "template", "timestamp"]) class TimeZoneTestCase(VerticaPythonIntegrationTestCase): def _test_ts(self, test_cases): with self._connect() as conn: cur = conn.cursor() for tc in test_cases: cur.execute("SELECT TO_TIMESTAMP('{0}', '{1}')".format(tc.string, tc.template)) res = cur.fetchone() self.assertEqual(tc.timestamp.toordinal(), res[0].toordinal()) def test_simple_ts_query(self): template = 'YYYY-MM-DD HH:MI:SS.MS' test_cases = [ TimeZoneTestingCase( string='2016-05-15 13:15:17.789', template=template, timestamp=datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17, microsecond=789000) ), ] self._test_ts(test_cases=test_cases) def test_simple_ts_with_tz_query(self): template = 'YYYY-MM-DD HH:MI:SS.MS TZ' test_cases = [ TimeZoneTestingCase( string='2016-05-15 13:15:17.789 UTC', template=template, timestamp=datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17, microsecond=789000, tzinfo=tz.tzutc()) ), ] self._test_ts(test_cases=test_cases) def test_simple_ts_with_offset_query(self): template = 'YYYY-MM-DD HH:MI:SS.MS+00' test_cases = [ TimeZoneTestingCase( string='2016-05-15 13:15:17.789 UTC', template=template, timestamp=datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17, microsecond=789000, tzinfo=tz.tzutc()) ), ] self._test_ts(test_cases=test_cases) exec(TimeZoneTestCase.createPrepStmtClass()) vertica-python-0.10.1/vertica_python/tests/integration_tests/test_unicode.py000066400000000000000000000106361357720411700275660ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .base import VerticaPythonIntegrationTestCase class UnicodeTestCase(VerticaPythonIntegrationTestCase): def test_unicode_query(self): value = u'\u16a0' query = u"SELECT '{0}'".format(value) with self._connect() as conn: cur = conn.cursor() cur.execute(query) res = cur.fetchone() self.assertResultEqual(value, res[0]) def test_unicode_list_parameter(self): values = [u'\u00f1', 'foo', 3] query = u"SELECT {0}".format(", ".join(["%s"] * len(values))) with self._connect() as conn: cur = conn.cursor() cur.execute(query, tuple(values)) results = cur.fetchone() for val, res in zip(values, results): self.assertResultEqual(val, res) def test_unicode_named_parameter_binding(self): values = [u'\u16b1', 'foo', 3] keys = [u'\u16a0', 'foo', 3] query = u"SELECT {0}".format(", ".join([u":{0}".format(key) for key in keys])) with self._connect() as conn: cur = conn.cursor() cur.execute(query, dict(zip(keys, values))) results = cur.fetchone() for val, res in zip(values, results): self.assertResultEqual(val, res) def test_string_query(self): value = u'test' query = u"SELECT '{0}'".format(value) with self._connect() as conn: cur = conn.cursor() cur.execute(query) res = cur.fetchone() self.assertEqual(value, res[0]) def test_string_named_parameter_binding(self): key = u'test' value = u'value' query = u"SELECT :{0}".format(key) with self._connect() as conn: cur = conn.cursor() cur.execute(query, {key: value}) res = cur.fetchone() self.assertResultEqual(value, res[0]) # unit test for issue #160 def test_null_named_parameter_binding(self): key = u'test' value = None query = u"SELECT :{0}".format(key) with self._connect() as conn: cur = conn.cursor() cur.execute(query, {key: value}) res = cur.fetchone() self.assertResultEqual(value, res[0]) # unit test for issue #160 def test_null_list_parameter(self): values = [u'\u00f1', 'foo', None] query = u"SELECT {0}".format(", ".join(["%s"] * len(values))) with self._connect() as conn: cur = conn.cursor() cur.execute(query, tuple(values)) results = cur.fetchone() for val, res in zip(values, results): self.assertResultEqual(val, res) vertica-python-0.10.1/vertica_python/tests/unit_tests/000077500000000000000000000000001357720411700231555ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/tests/unit_tests/__init__.py000066400000000000000000000033411357720411700252670ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE.vertica-python-0.10.1/vertica_python/tests/unit_tests/base.py000066400000000000000000000045241357720411700244460ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import pytest from ..common.base import VerticaPythonTestCase @pytest.mark.unit_tests class VerticaPythonUnitTestCase(VerticaPythonTestCase): """ Base class for tests that do not require database connection; simple unit testing of individual classes and functions """ @classmethod def setUpClass(cls): cls.test_config = cls._load_test_config(['log_dir', 'log_level']) cls._setup_logger('unit_tests', cls.test_config['log_dir'], cls.test_config['log_level']) @classmethod def tearDownClass(cls): pass vertica-python-0.10.1/vertica_python/tests/unit_tests/test_logging.py000066400000000000000000000031771357720411700262240ustar00rootroot00000000000000# Copyright (c) 2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import import logging import os from ...vertica.log import VerticaLogging from .base import VerticaPythonUnitTestCase class LoggingTestCase(VerticaPythonUnitTestCase): def test_file_handler(self): logger_name = "test_file_handler" logger = logging.getLogger(logger_name) self.assertNotEqual(logging.getLevelName(logger.getEffectiveLevel()), 'DEBUG') log_file = os.path.join(self.test_config['log_dir'], 'test_file_handler.log') VerticaLogging.setup_logging(logger_name, log_file, 'DEBUG') self.assertEqual(len(logger.handlers), 1) self.assertEqual(logging.getLevelName(logger.getEffectiveLevel()), 'DEBUG') def test_missing_file(self): logger_name = "test_missing_file" logger = logging.getLogger(logger_name) VerticaLogging.setup_logging(logger_name, None, 'DEBUG') self.assertEqual(len(logger.handlers), 0) self.assertEqual(logging.getLevelName(logger.getEffectiveLevel()), 'DEBUG') vertica-python-0.10.1/vertica_python/tests/unit_tests/test_notice.py000066400000000000000000000062671357720411700260620ustar00rootroot00000000000000# Copyright (c) 2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import import mock from .base import VerticaPythonUnitTestCase from ...vertica.messages import NoticeResponse from ...errors import QueryError class NoticeTestCase(VerticaPythonUnitTestCase): SAMPLE_DATA = {b'S': 'FATAL', b'H': 'This is a test hint', b'L': '9999', b'M': 'Failure is on purpose'} @mock.patch.object(NoticeResponse, '_unpack_data') def test_error_message(self, mock_unpack_data): mock_unpack_data.return_value = NoticeTestCase.SAMPLE_DATA notice = NoticeResponse(b'ignored-due-to-mock') self.assertEqual( notice.error_message(), 'Severity: FATAL, Message: Failure is on purpose, Hint: This is a test hint, Line: 9999' ) @mock.patch.object(NoticeResponse, '_unpack_data') def test_attribute_properties(self, mock_unpack_data): mock_unpack_data.return_value = NoticeTestCase.SAMPLE_DATA notice = NoticeResponse(b'ignored-due-to-mock') self.assertEqual(notice.severity, 'FATAL') self.assertEqual(notice.hint, 'This is a test hint') # yes, line is still a string. self.assertEqual(notice.line, '9999') self.assertEqual(notice.message, 'Failure is on purpose') self.assertIsNone(notice.detail) self.assertIsNone(notice.sqlstate) @mock.patch.object(NoticeResponse, '_unpack_data') def test_labeled_values(self, mock_unpack_data): mock_unpack_data.return_value = NoticeTestCase.SAMPLE_DATA notice = NoticeResponse(b'ignored-due-to-mock') self.assertEqual(notice.values, { 'Severity': 'FATAL', 'Hint': 'This is a test hint', 'Line': '9999', 'Message': 'Failure is on purpose'}) @mock.patch.object(NoticeResponse, '_unpack_data') def test_query_error(self, mock_unpack_data): mock_unpack_data.return_value = NoticeTestCase.SAMPLE_DATA notice = NoticeResponse(b'ignored-due-to-mock') query_error = QueryError(notice, 'Select Fake();') self.assertEqual(query_error.severity, 'FATAL') self.assertEqual(query_error.hint, 'This is a test hint') self.assertEqual(query_error.line, '9999') self.assertEqual(query_error.message, 'Failure is on purpose') self.assertIsNone(query_error.detail) self.assertIsNone(query_error.sqlstate) self.assertEqual( str(query_error), 'Severity: FATAL, Message: Failure is on purpose, Hint: This is a test hint, Line: 9999, SQL: \'Select Fake();\'') vertica-python-0.10.1/vertica_python/tests/unit_tests/test_parsedsn.py000066400000000000000000000075761357720411700264240ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import from .base import VerticaPythonUnitTestCase from ...vertica.connection import parse_dsn class ParseDSNTestCase(VerticaPythonUnitTestCase): def test_basic(self): dsn = 'vertica://admin@192.168.10.1' expected = {'host': '192.168.10.1', 'user': 'admin'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) dsn = 'vertica://mike@127.0.0.1/db1' expected = {'host': '127.0.0.1', 'user': 'mike', 'database': 'db1'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) dsn = 'vertica://john:pwd@example.com:5433/db1' expected = {'database': 'db1', 'host': 'example.com', 'password': 'pwd', 'port': 5433, 'user': 'john'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) def test_str_arguments(self): dsn = ('vertica://john:pwd@localhost:5433/db1?' 'session_label=vpclient&unicode_error=strict&' 'log_path=/home/admin/vClient.log&log_level=DEBUG&' 'kerberos_service_name=krb_service&kerberos_host_name=krb_host') expected = {'database': 'db1', 'host': 'localhost', 'user': 'john', 'password': 'pwd', 'port': 5433, 'log_level': 'DEBUG', 'session_label': 'vpclient', 'unicode_error': 'strict', 'log_path': '/home/admin/vClient.log', 'kerberos_service_name': 'krb_service', 'kerberos_host_name': 'krb_host'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) def test_boolean_arguments(self): dsn = ('vertica://mike@127.0.0.1/db1?connection_load_balance=True&' 'use_prepared_statements=0&ssl=false') expected = {'database': 'db1', 'connection_load_balance': True, 'use_prepared_statements': False, 'ssl': False, 'host': '127.0.0.1', 'user': 'mike'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) def test_numeric_arguments(self): dsn = 'vertica://mike@127.0.0.1/db1?connection_timeout=1.5&log_level=10' expected = {'host': '127.0.0.1', 'user': 'mike', 'database': 'db1', 'connection_timeout': 1.5, 'log_level': 10} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) def test_ignored_arguments(self): # Invalid value dsn = ('vertica://mike@127.0.0.1/db1?ssl=ssl_context&' 'connection_load_balance=unknown') expected = {'host': '127.0.0.1', 'user': 'mike', 'database': 'db1'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) # Unsupported argument dsn = 'vertica://mike@127.0.0.1/db1?backup_server_node=123.456.789.123' expected = {'host': '127.0.0.1', 'user': 'mike', 'database': 'db1'} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) def test_overwrite_arguments(self): dsn = 'vertica://mike@127.0.0.1/db1?ssl=on&ssl=off&ssl=1&ssl=0' expected = {'host': '127.0.0.1', 'user': 'mike', 'database': 'db1', 'ssl': False} parsed = parse_dsn(dsn) self.assertDictEqual(expected, parsed) vertica-python-0.10.1/vertica_python/tests/unit_tests/test_timestamps.py000066400000000000000000000156471357720411700267710ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from collections import namedtuple from datetime import datetime from .base import VerticaPythonUnitTestCase from ...vertica.column import timestamp_parse TimestampTestingCase = namedtuple("TimestampTestingCase", ["string", "timestamp"]) class TimestampParsingTestCase(VerticaPythonUnitTestCase): def _test_timestamps(self, test_cases, msg=None): for tc in test_cases: self.assertEqual(timestamp_parse(tc.string), tc.timestamp, msg=msg) def test_timestamp_second_resolution(self): test_cases = [ # back to the future dates TimestampTestingCase( '1985-10-26 01:25:01', datetime(year=1985, month=10, day=26, hour=1, minute=25, second=1) ), TimestampTestingCase( '1955-11-12 22:55:02', datetime(year=1955, month=11, day=12, hour=22, minute=55, second=2) ), TimestampTestingCase( '2015-10-21 11:12:03', datetime(year=2015, month=10, day=21, hour=11, minute=12, second=3) ), TimestampTestingCase( '1885-01-01 01:02:04', datetime(year=1885, month=1, day=1, hour=1, minute=2, second=4) ), TimestampTestingCase( '1885-09-02 02:03:05', datetime(year=1885, month=9, day=2, hour=2, minute=3, second=5) ), ] self._test_timestamps(test_cases=test_cases, msg='timestamp second resolution') def test_timestamp_microsecond_resolution(self): test_cases = [ # back to the future dates TimestampTestingCase( '1985-10-26 01:25:01.1', datetime(year=1985, month=10, day=26, hour=1, minute=25, second=1, microsecond=100000) ), TimestampTestingCase( '1955-11-12 22:55:02.01', datetime(year=1955, month=11, day=12, hour=22, minute=55, second=2, microsecond=10000) ), TimestampTestingCase( '2015-10-21 11:12:03.001', datetime(year=2015, month=10, day=21, hour=11, minute=12, second=3, microsecond=1000) ), TimestampTestingCase( '1885-01-01 01:02:04.000001', datetime(year=1885, month=1, day=1, hour=1, minute=2, second=4, microsecond=1) ), TimestampTestingCase( '1885-09-02 02:03:05.002343', datetime(year=1885, month=9, day=2, hour=2, minute=3, second=5, microsecond=2343) ), ] self._test_timestamps(test_cases=test_cases, msg='timestamp microsecond resolution') def test_timestamp_year_over_9999_second_resolution(self): # Asserts that years over 9999 are truncated to 9999 test_cases = [ TimestampTestingCase( '19850-10-26 01:25:01', datetime(year=9999, month=10, day=26, hour=1, minute=25, second=1) ), TimestampTestingCase( '10000-11-12 22:55:02', datetime(year=9999, month=11, day=12, hour=22, minute=55, second=2) ), TimestampTestingCase( '9999-10-21 11:12:03', datetime(year=9999, month=10, day=21, hour=11, minute=12, second=3) ), TimestampTestingCase( '18850-01-01 01:02:04', datetime(year=9999, month=1, day=1, hour=1, minute=2, second=4) ), TimestampTestingCase( '18850-09-02 02:03:05', datetime(year=9999, month=9, day=2, hour=2, minute=3, second=5) ), ] self._test_timestamps(test_cases=test_cases, msg='timestamp past 9999 second resolution') def test_timestamp_year_over_9999_microsecond_resolution(self): test_cases = [ TimestampTestingCase( '19850-10-26 01:25:01.1', datetime(year=9999, month=10, day=26, hour=1, minute=25, second=1, microsecond=100000) ), TimestampTestingCase( '10000-11-12 22:55:02.01', datetime(year=9999, month=11, day=12, hour=22, minute=55, second=2, microsecond=10000) ), TimestampTestingCase( '9999-10-21 11:12:03.001', datetime(year=9999, month=10, day=21, hour=11, minute=12, second=3, microsecond=1000) ), TimestampTestingCase( '18850-01-01 01:02:04.000001', datetime(year=9999, month=1, day=1, hour=1, minute=2, second=4, microsecond=1) ), TimestampTestingCase( '18850-09-02 02:03:05.002343', datetime(year=9999, month=9, day=2, hour=2, minute=3, second=5, microsecond=2343) ), ] self._test_timestamps(test_cases=test_cases, msg='timestamp past 9999 microsecond resolution') vertica-python-0.10.1/vertica_python/vertica/000077500000000000000000000000001357720411700212475ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/vertica/__init__.py000066400000000000000000000033421357720411700233620ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. vertica-python-0.10.1/vertica_python/vertica/column.py000066400000000000000000000165511357720411700231260ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import re from collections import namedtuple from datetime import date, datetime from decimal import Decimal from uuid import UUID # noinspection PyCompatibility,PyUnresolvedReferences from builtins import str from dateutil import parser, tz from .. import errors from ..datatypes import VerticaType, getDisplaySize, getPrecision, getScale from ..compat import as_str, as_text YEARS_RE = re.compile(r"^([0-9]+)-") # these methods are bad... # # a few timestamp with tz examples: # 2013-01-01 00:00:00 # 2013-01-01 00:00:00+00 # 2013-01-01 00:00:00.01+00 # 2013-01-01 00:00:00.00001+00 # # Vertica stores all data in UTC: # "TIMESTAMP WITH TIMEZONE (TIMESTAMPTZ) data is stored in GMT (UTC) by # converting data from the current local time zone to GMT." # Vertica fetches data in local timezone: # "When TIMESTAMPTZ data is used, data is converted back to use the current # local time zone" # If vertica boxes are on UTC, you should never have a non +00 offset (as # far as I can tell) ie. inserting '2013-01-01 00:00:00.01 EST' to a # timestamptz type stores: 2013-01-01 05:00:00.01+00 # select t AT TIMEZONE 'America/New_York' returns: 2012-12-31 19:00:00.01 def timestamp_parse(s): s = as_str(s) try: dt = _timestamp_parse(s) except ValueError: # Value error, year might be over 9999 year_match = YEARS_RE.match(s) if year_match: year = year_match.groups()[0] dt = _timestamp_parse_without_year(s[len(year) + 1:]) dt = dt.replace(year=min(int(year), 9999)) else: raise errors.DataError('Timestamp value not supported: %s' % s) return dt def _timestamp_parse(s): if len(s) == 19: return datetime.strptime(s, '%Y-%m-%d %H:%M:%S') return datetime.strptime(s, '%Y-%m-%d %H:%M:%S.%f') def _timestamp_parse_without_year(s): if len(s) == 14: return datetime.strptime(s, '%m-%d %H:%M:%S') return datetime.strptime(s, '%m-%d %H:%M:%S.%f') def timestamp_tz_parse(s): s = as_str(s) # if timezone is simply UTC... if s.endswith('+00'): # remove time zone ts = timestamp_parse(s[:-3].encode(encoding='utf-8', errors='strict')) ts = ts.replace(tzinfo=tz.tzutc()) return ts # other wise do a real parse (slower) return parser.parse(s) def date_parse(s): """ Parses value of a DATE type. :param s: string to parse into date :return: an instance of datetime.date :raises NotSupportedError when a date Before Christ is encountered """ s = as_str(s) if s.endswith(' BC'): raise errors.NotSupportedError('Dates Before Christ are not supported. Got: {0}'.format(s)) # Value error, year might be over 9999 return date(*map(lambda x: min(int(x), 9999), s.split('-'))) def time_parse(s): s = as_str(s) if len(s) == 8: return datetime.strptime(s, '%H:%M:%S').time() return datetime.strptime(s, '%H:%M:%S.%f').time() # Type casting of SQL types bytes representation into Python objects def vertica_type_cast(type_code, unicode_error): typecaster = { VerticaType.UNKNOWN: None, VerticaType.BOOL: lambda s: s == b't', VerticaType.INT8: lambda s: int(s), VerticaType.FLOAT8: lambda s: float(s), VerticaType.CHAR: lambda s: str(s, encoding='utf-8', errors=unicode_error), VerticaType.VARCHAR: lambda s: str(s, encoding='utf-8', errors=unicode_error), VerticaType.DATE: date_parse, VerticaType.TIME: time_parse, VerticaType.TIMESTAMP: timestamp_parse, VerticaType.TIMESTAMPTZ: timestamp_tz_parse, VerticaType.INTERVAL: None, VerticaType.TIMETZ: None, VerticaType.NUMERIC: lambda s: Decimal(str(s, encoding='utf-8', errors=unicode_error)), VerticaType.VARBINARY: None, VerticaType.UUID: lambda s: UUID(str(s, encoding='utf-8', errors=unicode_error)), VerticaType.INTERVALYM: None, VerticaType.LONGVARCHAR: lambda s: str(s, encoding='utf-8', errors=unicode_error), VerticaType.LONGVARBINARY: None, VerticaType.BINARY: None } return typecaster.get(type_code, None) ColumnTuple = namedtuple('Column', ['name', 'type_code', 'display_size', 'internal_size', 'precision', 'scale', 'null_ok']) class Column(object): def __init__(self, col, unicode_error='strict'): # Describe one query result column self.name = col['name'] self.type_code = col['data_type_oid'] self.type_name = col['data_type_name'] self.display_size = getDisplaySize(col['data_type_oid'], col['type_modifier']) self.internal_size = col['data_type_size'] self.precision = getPrecision(col['data_type_oid'], col['type_modifier']) self.scale = getScale(col['data_type_oid'], col['type_modifier']) self.null_ok = col['null_ok'] self.is_identity = col['is_identity'] self.converter = vertica_type_cast(self.type_code, unicode_error) self.props = ColumnTuple(self.name, self.type_code, self.display_size, self.internal_size, self.precision, self.scale, self.null_ok) def convert(self, s): if s is None: return return self.converter(s) if self.converter is not None else s def __str__(self): return as_str(str(self.props)) def __unicode__(self): return as_text(str(self.props)) def __repr__(self): return as_str(str(self.props)) def __iter__(self): for prop in self.props: yield prop def __getitem__(self, key): return self.props[key] vertica-python-0.10.1/vertica_python/vertica/connection.py000066400000000000000000000762521357720411700237740ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import base64 import logging import socket import ssl import getpass import uuid from struct import unpack from collections import deque, namedtuple # noinspection PyCompatibility,PyUnresolvedReferences from builtins import str from six import raise_from, string_types, integer_types, PY2 if PY2: from urlparse import urlparse, parse_qs else: from urllib.parse import urlparse, parse_qs import vertica_python from .. import errors from ..vertica import messages from ..vertica.cursor import Cursor from ..vertica.messages.message import BackendMessage, FrontendMessage from ..vertica.messages.frontend_messages import CancelRequest from ..vertica.log import VerticaLogging DEFAULT_HOST = 'localhost' DEFAULT_PORT = 5433 DEFAULT_PASSWORD = '' DEFAULT_BACKUP_SERVER_NODE = [] DEFAULT_KRB_SERVICE_NAME = 'vertica' DEFAULT_LOG_LEVEL = logging.WARNING DEFAULT_LOG_PATH = 'vertica_python.log' try: DEFAULT_USER = getpass.getuser() except Exception as e: DEFAULT_USER = None print("WARN: Cannot get the login user name: {}".format(str(e))) def connect(**kwargs): """Opens a new connection to a Vertica database.""" return Connection(kwargs) def parse_dsn(dsn): """Parse connection string into a dictionary of keywords and values. Connection string format: vertica://:@:/?k1=v1&k2=v2&... """ url = urlparse(dsn) if url.scheme != 'vertica': raise ValueError("Only vertica:// scheme is supported.") # Ignore blank/invalid values result = {k: v for k, v in ( ('host', url.hostname), ('port', url.port), ('user', url.username), ('password', url.password), ('database', url.path[1:])) if v } for key, value in parse_qs(url.query).items(): if key == 'backup_server_node': continue elif key in ('connection_load_balance', 'use_prepared_statements', 'ssl'): lower = value[-1].lower() if lower in ('true', 'on', '1'): result[key] = True elif lower in ('false', 'off', '0'): result[key] = False elif key == 'connection_timeout': result[key] = float(value[-1]) elif key == 'log_level' and value[-1].isdigit(): result[key] = int(value[-1]) else: result[key] = value[-1] return result _AddressEntry = namedtuple('_AddressEntry', ['host', 'resolved', 'data']) class _AddressList(object): def __init__(self, host, port, backup_nodes, logger): """Creates a new deque with the primary host first, followed by any backup hosts""" self._logger = logger # Items in address_deque are _AddressEntry values. # host is the original hostname/ip, used by SSL option check_hostname # - when resolved is False, data is port # - when resolved is True, data is the 5-tuple from socket.getaddrinfo # This allows for lazy resolution. Seek peek() for more. self.address_deque = deque() # load primary host into address_deque self._append(host, port) # load backup nodes into address_deque if not isinstance(backup_nodes, list): err_msg = 'Connection option "backup_server_node" must be a list' self._logger.error(err_msg) raise TypeError(err_msg) # Each item in backup_nodes should be either # a host name or IP address string (using default port) or # a (host, port) tuple for node in backup_nodes: if isinstance(node, string_types): self._append(node, DEFAULT_PORT) elif isinstance(node, tuple) and len(node) == 2: self._append(node[0], node[1]) else: err_msg = ('Each item of connection option "backup_server_node"' ' must be a host string or a (host, port) tuple') self._logger.error(err_msg) raise TypeError(err_msg) self._logger.debug('Address list: {0}'.format(list(self.address_deque))) def _append(self, host, port): if not isinstance(host, string_types): err_msg = 'Host must be a string: invalid value: {0}'.format(host) self._logger.error(err_msg) raise TypeError(err_msg) if not isinstance(port, (string_types, integer_types)): err_msg = 'Port must be an integer or a string: invalid value: {0}'.format(port) self._logger.error(err_msg) raise TypeError(err_msg) elif isinstance(port, string_types): try: port = int(port) except ValueError as e: err_msg = 'Port "{0}" is not a valid string: {1}'.format(port, e) self._logger.error(err_msg) raise ValueError(err_msg) if port < 0 or port > 65535: err_msg = 'Invalid port number: {0}'.format(port) self._logger.error(err_msg) raise ValueError(err_msg) self.address_deque.append(_AddressEntry(host=host, resolved=False, data=port)) def push(self, host, port): self.address_deque.appendleft(_AddressEntry(host=host, resolved=False, data=port)) def pop(self): self.address_deque.popleft() def peek(self): # do lazy DNS resolution, returning the leftmost socket.getaddrinfo result if len(self.address_deque) == 0: return None while len(self.address_deque) > 0: self._logger.debug('Peek at address list: {0}'.format(list(self.address_deque))) entry = self.address_deque[0] if entry.resolved: # return a resolved sockaddrinfo return entry.data else: # DNS resolve a single host name to multiple IP addresses self.address_deque.popleft() host, port = entry.host, entry.data try: resolved_hosts = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) except Exception as e: self._logger.warning('Error resolving host "{0}" on port {1}: {2}'.format(host, port, e)) continue # add resolved addrinfo (AF_INET and AF_INET6 only) to deque for addrinfo in reversed(resolved_hosts): if addrinfo[0] in (socket.AF_INET, socket.AF_INET6): self.address_deque.appendleft(_AddressEntry( host=host, resolved=True, data=addrinfo)) return None def peek_host(self): # returning the leftmost host result self._logger.debug('Peek host at address list: {0}'.format(list(self.address_deque))) if len(self.address_deque) == 0: return None return self.address_deque[0].host def _generate_session_label(): return '{type}-{version}-{id}'.format( type='vertica-python', version=vertica_python.__version__, id=uuid.uuid1() ) class Connection(object): def __init__(self, options=None): self.parameters = {} self.session_id = None self.backend_pid = None self.backend_key = None self.transaction_status = None self.socket = None options = options or {} self.options = parse_dsn(options['dsn']) if 'dsn' in options else {} self.options.update({key: value for key, value in options.items() \ if key != 'dsn' and value is not None}) # Set up connection logger logger_name = 'vertica_{0}_{1}'.format(id(self), str(uuid.uuid4())) # must be a unique value self._logger = logging.getLogger(logger_name) if 'log_level' not in self.options and 'log_path' not in self.options: # logger is disabled by default self._logger.disabled = True else: self.options.setdefault('log_level', DEFAULT_LOG_LEVEL) self.options.setdefault('log_path', DEFAULT_LOG_PATH) VerticaLogging.setup_logging(logger_name, self.options['log_path'], self.options['log_level'], id(self)) self.options.setdefault('host', DEFAULT_HOST) self.options.setdefault('port', DEFAULT_PORT) if 'user' not in self.options: if DEFAULT_USER: self.options['user'] = DEFAULT_USER else: msg = 'Connection option "user" is required' self._logger.error(msg) raise KeyError(msg) self.options.setdefault('database', self.options['user']) self.options.setdefault('password', DEFAULT_PASSWORD) self.options.setdefault('session_label', _generate_session_label()) self.options.setdefault('backup_server_node', DEFAULT_BACKUP_SERVER_NODE) self.options.setdefault('kerberos_service_name', DEFAULT_KRB_SERVICE_NAME) # Kerberos authentication hostname defaults to the host value here so # the correct value cannot be overwritten by load balancing or failover self.options.setdefault('kerberos_host_name', self.options['host']) self.address_list = _AddressList(self.options['host'], self.options['port'], self.options['backup_server_node'], self._logger) # we only support one cursor per connection self.options.setdefault('unicode_error', None) self._cursor = Cursor(self, self._logger, cursor_type=None, unicode_error=self.options['unicode_error']) # knob for using server-side prepared statements self.options.setdefault('use_prepared_statements', False) self._logger.debug('Connection prepared statements is {}'.format( 'enabled' if self.options['use_prepared_statements'] else 'disabled')) self._logger.info('Connecting as user "{}" to database "{}" on host "{}" with port {}'.format( self.options['user'], self.options['database'], self.options['host'], self.options['port'])) self.startup_connection() self._logger.info('Connection is ready') ############################################# # supporting `with` statements ############################################# def __enter__(self): return self def __exit__(self, type_, value, traceback): try: # get the transaction status if not self.closed(): self.cursor().flush_to_query_ready() # if there's no outstanding transaction, we can simply close the connection if self.transaction_status in (None, 'in_transaction'): return if type_ is not None: self.rollback() else: self.commit() finally: self.close() ############################################# # dbapi methods ############################################# def close(self): self._logger.info('Close the connection') try: self.write(messages.Terminate()) finally: self.close_socket() def commit(self): if self.closed(): raise errors.ConnectionError('Connection is closed') cur = self.cursor() cur.execute('COMMIT;') def rollback(self): if self.closed(): raise errors.ConnectionError('Connection is closed') cur = self.cursor() cur.execute('ROLLBACK;') def cursor(self, cursor_type=None): if self.closed(): raise errors.ConnectionError('Connection is closed') if self._cursor.closed(): self._cursor._closed = False # let user change type if they want? self._cursor.cursor_type = cursor_type return self._cursor ############################################# # non-dbapi methods ############################################# def cancel(self): """Cancel the current database operation. This can be called from a different thread than the one currently executing a database operation. """ if self.closed(): raise errors.ConnectionError('Connection is closed') self._logger.info('Canceling the current database operation') # Must create a new socket connection to the server temp_socket = self.establish_socket_connection(self.address_list) self.write(CancelRequest(self.backend_pid, self.backend_key), temp_socket) temp_socket.close() self._logger.info('Cancel request issued') def opened(self): return (self.socket is not None and self.backend_pid is not None and self.transaction_status is not None) def closed(self): return not self.opened() def __str__(self): safe_options = {key: value for key, value in self.options.items() if key != 'password'} s1 = " %s', messages.LoadBalanceRequest()) raw_socket.sendall(messages.LoadBalanceRequest().get_message()) response = raw_socket.recv(1) if response == b'Y': size = unpack('!I', raw_socket.recv(4))[0] if size < 4: err_msg = "Bad message size: {0}".format(size) self._logger.error(err_msg) raise errors.MessageError(err_msg) res = BackendMessage.from_type(type_=response, data=raw_socket.recv(size - 4)) self._logger.debug('<= %s', res) host = res.get_host() port = res.get_port() self._logger.info('Load balancing to host "{0}" on port {1}'.format(host, port)) peer = raw_socket.getpeername() socket_host, socket_port = peer[0], peer[1] if host == socket_host and port == socket_port: self._logger.info('Already connecting to host "{0}" on port {1}. Ignore load balancing.'.format(host, port)) return raw_socket # Push the new host onto the address list before connecting again. Note that this # will leave the originally-specified host as the first failover possibility. self.address_list.push(host, port) raw_socket.close() raw_socket = self.establish_socket_connection(self.address_list) else: self._logger.debug('<= LoadBalanceResponse: %s', response) self._logger.warning("Load balancing requested but not supported by server") return raw_socket def enable_ssl(self, raw_socket, ssl_options): # Send SSL request and read server response self._logger.debug('=> %s', messages.SslRequest()) raw_socket.sendall(messages.SslRequest().get_message()) response = raw_socket.recv(1) self._logger.debug('<= SslResponse: %s', response) if response == b'S': self._logger.info('Enabling SSL') try: if isinstance(ssl_options, ssl.SSLContext): server_host = self.address_list.peek_host() if server_host is None: # This should not happen msg = 'Cannot get the connected server host while enabling SSL' self._logger.error(msg) raise errors.ConnectionError(msg) raw_socket = ssl_options.wrap_socket(raw_socket, server_hostname=server_host) else: raw_socket = ssl.wrap_socket(raw_socket) except ssl.CertificateError as e: raise_from(errors.ConnectionError(str(e)), e) except ssl.SSLError as e: raise_from(errors.ConnectionError(str(e)), e) else: err_msg = "SSL requested but not supported by server" self._logger.error(err_msg) raise errors.SSLNotSupported(err_msg) return raw_socket def establish_socket_connection(self, address_list): """Given a list of database node addresses, establish the socket connection to the database server. Return a connected socket object. """ addrinfo = address_list.peek() raw_socket = None last_exception = None # Failover: loop to try all addresses while addrinfo: (family, socktype, proto, canonname, sockaddr) = addrinfo last_exception = None # _AddressList filters all addrs to AF_INET and AF_INET6, which both # have host and port as values 0, 1 in the sockaddr tuple. host = sockaddr[0] port = sockaddr[1] self._logger.info('Establishing connection to host "{0}" on port {1}'.format(host, port)) try: raw_socket = self.create_socket(family) raw_socket.connect(sockaddr) break except Exception as e: self._logger.info('Failed to connect to host "{0}" on port {1}: {2}'.format(host, port, e)) last_exception = e address_list.pop() addrinfo = address_list.peek() raw_socket.close() # all of the addresses failed if raw_socket is None or last_exception: err_msg = 'Failed to establish a connection to the primary server or any backup address.' self._logger.error(err_msg) raise errors.ConnectionError(err_msg) return raw_socket def ssl(self): return self.socket is not None and isinstance(self.socket, ssl.SSLSocket) def write(self, message, vsocket=None): if not isinstance(message, FrontendMessage): raise TypeError("invalid message: ({0})".format(message)) if vsocket is None: vsocket = self._socket() self._logger.debug('=> %s', message) try: for data in message.fetch_message(): try: vsocket.sendall(data) except Exception: self._logger.error("couldn't send message") raise except Exception as e: self.close_socket() self._logger.error(str(e)) raise def close_socket(self): try: if self.socket is not None: self._socket().close() finally: self.reset_values() def reset_connection(self): self.close() self.startup_connection() def is_asynchronous_message(self, message): # Check if it is an asynchronous response message # Note: ErrorResponse is a subclass of NoticeResponse return (isinstance(message, messages.ParameterStatus) or (isinstance(message, messages.NoticeResponse) and not isinstance(message, messages.ErrorResponse))) def handle_asynchronous_message(self, message): if isinstance(message, messages.ParameterStatus): if message.name == 'protocol_version': message.value = int(message.value) self.parameters[message.name] = message.value elif (isinstance(message, messages.NoticeResponse) and not isinstance(message, messages.ErrorResponse)): if getattr(self, 'notice_handler', None) is not None: self.notice_handler(message) else: self._logger.warning(message.error_message()) def read_message(self): while True: try: type_ = self.read_bytes(1) size = unpack('!I', self.read_bytes(4))[0] if size < 4: raise errors.MessageError("Bad message size: {0}".format(size)) message = BackendMessage.from_type(type_, self.read_bytes(size - 4)) self._logger.debug('<= %s', message) self.handle_asynchronous_message(message) # handle transaction status if isinstance(message, messages.ReadyForQuery): self.transaction_status = message.transaction_status except (SystemError, IOError) as e: self.close_socket() # noinspection PyTypeChecker self._logger.error(e) raise_from(errors.ConnectionError(str(e)), e) if not self.is_asynchronous_message(message): break return message def read_expected_message(self, expected_types, error_handler=None): # Reads a message and does some basic error handling. # expected_types must be a class (e.g. messages.BindComplete) or a tuple of classes message = self.read_message() if isinstance(message, expected_types): return message elif isinstance(message, messages.ErrorResponse): if error_handler is not None: error_handler(message) else: raise errors.DatabaseError(message.error_message()) else: msg = 'Received unexpected message type: {}. '.format(type(message).__name__) if isinstance(expected_types, tuple): msg += 'Expected types: {}'.format(", ".join([t.__name__ for t in expected_types])) else: msg += 'Expected type: {}'.format(expected_types.__name__) self._logger.error(msg) raise errors.MessageError(msg) def read_bytes(self, n): results = bytes() while len(results) < n: bytes_ = self._socket().recv(n - len(results)) if not bytes_: raise errors.ConnectionError("Connection closed by Vertica") results += bytes_ return results def send_GSS_response_and_receive_challenge(self, response): # Send the GSS response data to the vertica server token = base64.b64decode(response) self.write(messages.Password(token, messages.Authentication.GSS)) # Receive the challenge from the vertica server message = self.read_expected_message(messages.Authentication) if message.code != messages.Authentication.GSS_CONTINUE: msg = ('Received unexpected message type: Authentication(type={}).' ' Expected type: Authentication(type={})'.format( message.code, messages.Authentication.GSS_CONTINUE)) self._logger.error(msg) raise errors.MessageError(msg) return message.auth_data def make_GSS_authentication(self): try: import kerberos except ImportError as e: raise errors.ConnectionError("{}\nCannot make a Kerberos " "authentication because no Kerberos package is installed. " "Get it with 'pip install kerberos'.".format(str(e))) # Set GSS flags gssflag = (kerberos.GSS_C_DELEG_FLAG | kerberos.GSS_C_MUTUAL_FLAG | kerberos.GSS_C_SEQUENCE_FLAG | kerberos.GSS_C_REPLAY_FLAG) # Generate the GSS-style service principal name service_principal = "{}@{}".format(self.options['kerberos_service_name'], self.options['kerberos_host_name']) # Initializes a context object with a service principal self._logger.info('Initializing a context for GSSAPI client-side ' 'authentication with service principal {}'.format(service_principal)) try: result, context = kerberos.authGSSClientInit(service_principal, gssflags=gssflag) except kerberos.GSSError as err: msg = "GSSAPI initialization error: {}".format(str(err)) self._logger.error(msg) raise errors.KerberosError(msg) if result != kerberos.AUTH_GSS_COMPLETE: msg = ('Failed to initialize a context for GSSAPI client-side ' 'authentication with service principal {}'.format(service_principal)) self._logger.error(msg) raise errors.KerberosError(msg) # Processes GSSAPI client-side steps try: challenge = b'' while True: self._logger.info('Processing a single GSSAPI client-side step') challenge = base64.b64encode(challenge).decode("utf-8") result = kerberos.authGSSClientStep(context, challenge) if result == kerberos.AUTH_GSS_COMPLETE: self._logger.info('Result: GSSAPI step complete') break elif result == kerberos.AUTH_GSS_CONTINUE: self._logger.info('Result: GSSAPI step continuation') # Get the response from the last successful GSSAPI client-side step response = kerberos.authGSSClientResponse(context) challenge = self.send_GSS_response_and_receive_challenge(response) else: msg = "GSSAPI client-side step error status {}".format(result) self._logger.error(msg) raise errors.KerberosError(msg) except kerberos.GSSError as err: msg = "GSSAPI client-side step error: {}".format(str(err)) self._logger.error(msg) raise errors.KerberosError(msg) def startup_connection(self): user = self.options['user'] database = self.options['database'] session_label = self.options['session_label'] os_user_name = DEFAULT_USER if DEFAULT_USER else '' password = self.options['password'] self.write(messages.Startup(user, database, session_label, os_user_name)) while True: message = self.read_message() if isinstance(message, messages.Authentication): if message.code == messages.Authentication.OK: self._logger.info("User {} successfully authenticated" .format(self.options['user'])) elif message.code == messages.Authentication.CHANGE_PASSWORD: msg = "The password for user {} has expired".format(self.options['user']) self._logger.error(msg) raise errors.ConnectionError(msg) elif message.code == messages.Authentication.PASSWORD_GRACE: self._logger.warning('The password for user {} will expire soon.' ' Please consider changing it.'.format(self.options['user'])) elif message.code == messages.Authentication.GSS: self.make_GSS_authentication() else: self.write(messages.Password(password, message.code, {'user': user, 'salt': getattr(message, 'salt', None), 'usersalt': getattr(message, 'usersalt', None)})) elif isinstance(message, messages.BackendKeyData): self.backend_pid = message.pid self.backend_key = message.key elif isinstance(message, messages.ReadyForQuery): break elif isinstance(message, messages.ErrorResponse): self._logger.error(message.error_message()) raise errors.ConnectionError(message.error_message()) else: msg = "Received unexpected startup message: {0}".format(message) self._logger.error(msg) raise errors.MessageError(msg) vertica-python-0.10.1/vertica_python/vertica/cursor.py000066400000000000000000000635251357720411700231510ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import datetime import inspect import re from io import IOBase from tempfile import NamedTemporaryFile, SpooledTemporaryFile, TemporaryFile from uuid import UUID from collections import OrderedDict # _TemporaryFileWrapper is an undocumented implementation detail, so # import defensively. try: from tempfile import _TemporaryFileWrapper except ImportError: _TemporaryFileWrapper = None import six # noinspection PyUnresolvedReferences,PyCompatibility from builtins import str from six import binary_type, text_type, string_types, BytesIO, StringIO from .. import errors from ..compat import as_text from ..vertica import messages from ..vertica.column import Column # A note regarding support for temporary files: # # Since Python 2.6, the tempfile module offers three kinds of temporary # files: # # * NamedTemporaryFile # * SpooledTemporaryFile # * TemporaryFile # # NamedTemporaryFile is not a class, but a function that returns # an instance of the tempfile._TemporaryFileWrapper class. # _TemporaryFileWrapper is a direct subclass of object. # # * https://github.com/python/cpython/blob/v3.8.0/Lib/tempfile.py#L546 # * https://github.com/python/cpython/blob/v3.8.0/Lib/tempfile.py#L450 # # SpooledTemporaryFile is a class that is a direct subclass of object. # # * https://github.com/python/cpython/blob/v3.8.0/Lib/tempfile.py#L623 # # TemporaryFile is a class that is either NamedTemporaryFile or an # indirect subclass of io.IOBase, depending on the platform. # # * https://bugs.python.org/issue33762 # * https://github.com/python/cpython/blob/v3.8.0/Lib/tempfile.py#L552-L555 # * https://github.com/python/cpython/blob/v3.8.0/Lib/tempfile.py#L606-L608 # * https://github.com/python/cpython/blob/v3.8.0/Lib/tempfile.py#L617-L618 # # As a result, for Python 2.6 and newer, it seems the best way to test # for a file-like object inclusive of temporary files is via: # # isinstance(obj, (IOBase, SpooledTemporaryFile, _TemporaryFileWrapper)) # Of the following "types", only include those that are classes in # file_type so that isinstance(obj, file_type) won't fail. As of Python # 3.8 only IOBase, SpooledTemporaryFile and _TemporaryFileWrapper are # classes, but if future Python versions implement NamedTemporaryFile # and TemporaryFile as classes, the following code should account for # that accordingly. file_type = tuple( type_ for type_ in [ IOBase, NamedTemporaryFile, SpooledTemporaryFile, TemporaryFile, _TemporaryFileWrapper, ] if inspect.isclass(type_) ) if six.PY2: # noinspection PyUnresolvedReferences file_type = file_type + (file,) NULL = "NULL" RE_NAME_BASE = u"[a-zA-Z_][\\w\\d\\$_]*" RE_NAME = u'(("{0}")|({0}))'.format(RE_NAME_BASE) RE_BASIC_INSERT_STAT = ( u"INSERT\\s+INTO\\s+(?P({0}\\.)?{0})" u"\\s*\\(\\s*(?P{0}(\\s*,\\s*{0})*)\\s*\\)" u"\\s+VALUES\\s*\\(\\s*(?P(.|\\s)*)\\s*\\)").format(RE_NAME) END_OF_RESULT_RESPONSES = (messages.CommandComplete, messages.PortalSuspended) class Cursor(object): # NOTE: this is used in executemany and is here for pandas compatibility _insert_statement = re.compile(RE_BASIC_INSERT_STAT, re.U | re.I) def __init__(self, connection, logger, cursor_type=None, unicode_error=None): self.connection = connection self._logger = logger self.cursor_type = cursor_type self.unicode_error = unicode_error if unicode_error is not None else 'strict' self._closed = False self._message = None self.operation = None self.prepared_sql = None # last statement been prepared self.prepared_name = "s0" self.error = None # # dbapi attributes # self.description = None self.rowcount = -1 self.arraysize = 1 ############################################# # supporting `with` statements ############################################# def __enter__(self): return self def __exit__(self, type_, value, traceback): self.close() ############################################# # dbapi methods ############################################# # noinspection PyMethodMayBeStatic def callproc(self, procname, parameters=None): raise errors.NotSupportedError('Cursor.callproc() is not implemented') def close(self): self._logger.info('Close the cursor') if not self.closed() and self.prepared_sql: self._close_prepared_statement() self._closed = True def execute(self, operation, parameters=None, use_prepared_statements=None): operation = as_text(operation) self.operation = operation if self.closed(): raise errors.InterfaceError('Cursor is closed') self.flush_to_query_ready() self.rowcount = -1 use_prepared = bool(self.connection.options['use_prepared_statements'] if use_prepared_statements is None else use_prepared_statements) if use_prepared: # Execute the SQL as prepared statement (server-side bindings) if parameters and not isinstance(parameters, (list, tuple)): raise TypeError("Execute parameters should be a list/tuple") # If the SQL has not been prepared, prepare the SQL if operation != self.prepared_sql: self._prepare(operation) self.prepared_sql = operation # the prepared statement is kept # Bind the parameters and execute self._execute_prepared_statement([parameters]) else: # Execute the SQL directly (client-side bindings) if parameters: operation = self.format_operation_with_parameters(operation, parameters) self._execute_simple_query(operation) return self def executemany(self, operation, seq_of_parameters, use_prepared_statements=None): operation = as_text(operation) self.operation = operation if not isinstance(seq_of_parameters, (list, tuple)): raise TypeError("seq_of_parameters should be list/tuple") if self.closed(): raise errors.InterfaceError('Cursor is closed') self.flush_to_query_ready() use_prepared = bool(self.connection.options['use_prepared_statements'] if use_prepared_statements is None else use_prepared_statements) if use_prepared: # Execute the SQL as prepared statement (server-side bindings) if len(seq_of_parameters) == 0: raise ValueError("seq_of_parameters should not be empty") if not all(isinstance(elem, (list, tuple)) for elem in seq_of_parameters): raise TypeError("Each seq_of_parameters element should be a list/tuple") # If the SQL has not been prepared, prepare the SQL if operation != self.prepared_sql: self._prepare(operation) self.prepared_sql = operation # the prepared statement is kept # Bind the parameters and execute self._execute_prepared_statement(seq_of_parameters) else: m = self._insert_statement.match(operation) if m: target = as_text(m.group('target')) variables = as_text(m.group('variables')) variables = ",".join([variable.strip().strip('"') for variable in variables.split(",")]) values = as_text(m.group('values')) values = ",".join([value.strip().strip('"') for value in values.split(",")]) seq_of_values = [self.format_operation_with_parameters(values, parameters, is_csv=True) for parameters in seq_of_parameters] data = "\n".join(seq_of_values) copy_autocommit = self.connection.parameters.get('auto_commit', 'on') copy_statement = ( u"COPY {0} ({1}) FROM STDIN DELIMITER ',' ENCLOSED BY '\"' " u"ENFORCELENGTH ABORT ON ERROR{2}").format(target, variables, " NO COMMIT" if copy_autocommit == 'off' else '') self.copy(copy_statement, data) else: raise NotImplementedError( "executemany is implemented for simple INSERT statements only") def fetchone(self): while True: if isinstance(self._message, messages.DataRow): if self.rowcount == -1: self.rowcount = 1 else: self.rowcount += 1 row = self.row_formatter(self._message) # fetch next message self._message = self.connection.read_message() return row elif isinstance(self._message, messages.RowDescription): self.description = [Column(fd, self.unicode_error) for fd in self._message.fields] elif isinstance(self._message, messages.ReadyForQuery): return None elif isinstance(self._message, END_OF_RESULT_RESPONSES): return None elif isinstance(self._message, messages.EmptyQueryResponse): pass elif isinstance(self._message, messages.ErrorResponse): raise errors.QueryError.from_error_response(self._message, self.operation) else: raise errors.MessageError('Unexpected fetchone() state: {}'.format( type(self._message).__name__)) self._message = self.connection.read_message() def fetchmany(self, size=None): if not size: size = self.arraysize results = [] while True: row = self.fetchone() if not row: break results.append(row) if len(results) >= size: break return results def fetchall(self): return list(self.iterate()) def nextset(self): """ Skip to the next available result set, discarding any remaining rows from the current result set. If there are no more result sets, this method returns False. Otherwise, it returns a True and subsequent calls to the fetch*() methods will return rows from the next result set. """ # skip any data for this set if exists self.flush_to_end_of_result() if self._message is None: return False elif isinstance(self._message, END_OF_RESULT_RESPONSES): # there might be another set, read next message to find out self._message = self.connection.read_message() if isinstance(self._message, messages.RowDescription): self.description = [Column(fd, self.unicode_error) for fd in self._message.fields] self._message = self.connection.read_message() return True elif isinstance(self._message, messages.BindComplete): self._message = self.connection.read_message() return True elif isinstance(self._message, messages.ReadyForQuery): return False elif isinstance(self._message, END_OF_RESULT_RESPONSES): # result of a DDL/transaction return True elif isinstance(self._message, messages.ErrorResponse): raise errors.QueryError.from_error_response(self._message, self.operation) else: raise errors.MessageError( 'Unexpected nextset() state after END_OF_RESULT_RESPONSES: {0}'.format(self._message)) elif isinstance(self._message, messages.ReadyForQuery): # no more sets left to be read return False else: raise errors.MessageError('Unexpected nextset() state: {0}'.format(self._message)) def setinputsizes(self, sizes): pass def setoutputsize(self, size, column=None): pass ############################################# # non-dbapi methods ############################################# def closed(self): return self._closed or self.connection.closed() def cancel(self): # Cancel is a session-level operation, cursor-level API does not make # sense. Keep this API for backward compatibility. raise errors.NotSupportedError( 'Cursor.cancel() is deprecated. Call Connection.cancel() ' 'to cancel the current database operation.') def iterate(self): row = self.fetchone() while row: yield row row = self.fetchone() def copy(self, sql, data, **kwargs): """ EXAMPLE: >> with open("/tmp/file.csv", "rb") as fs: >> cursor.copy("COPY table(field1,field2) FROM STDIN DELIMITER ',' ENCLOSED BY ''''", >> fs, buffer_size=65536) """ sql = as_text(sql) if self.closed(): raise errors.InterfaceError('Cursor is closed') self.flush_to_query_ready() if isinstance(data, binary_type): stream = BytesIO(data) elif isinstance(data, text_type): stream = StringIO(data) elif isinstance(data, file_type) or callable(getattr(data, 'read', None)): stream = data else: raise TypeError("Not valid type of data {0}".format(type(data))) self._logger.info(u'Execute COPY statement: [{}]'.format(sql)) self.connection.write(messages.Query(sql)) while True: message = self.connection.read_message() self._message = message if isinstance(message, messages.ErrorResponse): raise errors.QueryError.from_error_response(message, sql) elif isinstance(message, messages.ReadyForQuery): break elif isinstance(message, messages.CopyInResponse): self.connection.write(messages.CopyStream(stream, **kwargs)) self.connection.write(messages.CopyDone()) elif isinstance(message, messages.CommandComplete): pass else: raise errors.MessageError('Unexpected message: {0}'.format(message)) if self.error is not None: raise self.error ############################################# # internal ############################################# def flush_to_query_ready(self): # if the last message isn't empty or ReadyForQuery, read all remaining messages if self._message is None \ or isinstance(self._message, messages.ReadyForQuery): return while True: message = self.connection.read_message() if isinstance(message, messages.ReadyForQuery): self._message = message break def flush_to_end_of_result(self): # if the last message isn't empty or END_OF_RESULT_RESPONSES, # read messages until it is if (self._message is None or isinstance(self._message, messages.ReadyForQuery) or isinstance(self._message, END_OF_RESULT_RESPONSES)): return while True: message = self.connection.read_message() if isinstance(message, END_OF_RESULT_RESPONSES): self._message = message break def row_formatter(self, row_data): if self.cursor_type is None: return self.format_row_as_array(row_data) elif self.cursor_type in (list, 'list'): return self.format_row_as_array(row_data) elif self.cursor_type in (dict, 'dict'): return self.format_row_as_dict(row_data) else: raise TypeError('Unrecognized cursor_type: {0}'.format(self.cursor_type)) def format_row_as_dict(self, row_data): return OrderedDict( (self.description[idx].name, self.description[idx].convert(value)) for idx, value in enumerate(row_data.values) ) def format_row_as_array(self, row_data): return [self.description[idx].convert(value) for idx, value in enumerate(row_data.values)] # noinspection PyArgumentList def format_operation_with_parameters(self, operation, parameters, is_csv=False): operation = as_text(operation) if isinstance(parameters, dict): for key, param in six.iteritems(parameters): if not isinstance(key, string_types): key = str(key) key = as_text(key) if isinstance(param, (string_types, bytes)): param = self.format_quote(as_text(param), is_csv) elif isinstance(param, (datetime.datetime, datetime.date, datetime.time, UUID)): param = self.format_quote(as_text(str(param)), is_csv) elif param is None: param = '' if is_csv else NULL else: param = str(param) value = as_text(param) # Using a regex with word boundary to correctly handle params with similar names # such as :s and :start match_str = u":{0}\\b".format(key) operation = re.sub(match_str, lambda _: value, operation, flags=re.U) elif isinstance(parameters, (tuple, list)): tlist = [] for param in parameters: if isinstance(param, (string_types, bytes)): param = self.format_quote(as_text(param), is_csv) elif isinstance(param, (datetime.datetime, datetime.date, datetime.time, UUID)): param = self.format_quote(as_text(str(param)), is_csv) elif param is None: param = '' if is_csv else NULL else: param = str(param) value = as_text(param) tlist.append(value) operation = operation % tuple(tlist) else: raise TypeError("Argument 'parameters' must be dict or tuple/list") return operation def format_quote(self, param, is_csv): if is_csv: return u'"{0}"'.format(re.escape(param)) else: return u"'{0}'".format(param.replace(u"'", u"''")) def _execute_simple_query(self, query): """ Send the query to the server using the simple query protocol. Return True if this query contained no SQL (e.g. the string "--comment") """ self._logger.info(u'Execute simple query: [{}]'.format(query)) # All of the statements in the query are sent here in a single message self.connection.write(messages.Query(query)) # The first response could be a number of things: # ErrorResponse: Something went wrong on the server. # EmptyQueryResponse: The query being executed is empty. # RowDescription: This is the "normal" case when executing a query. # It marks the start of the results. # CommandComplete: This occurs when executing DDL/transactions. self._message = self.connection.read_message() if isinstance(self._message, messages.ErrorResponse): raise errors.QueryError.from_error_response(self._message, query) elif isinstance(self._message, messages.RowDescription): self.description = [Column(fd, self.unicode_error) for fd in self._message.fields] self._message = self.connection.read_message() if isinstance(self._message, messages.ErrorResponse): raise errors.QueryError.from_error_response(self._message, query) def _error_handler(self, msg): self.connection.write(messages.Sync()) raise errors.QueryError.from_error_response(msg, self.operation) def _prepare(self, query): """ Send the query to be prepared to the server. The server will parse the query and return some metadata. """ self._logger.info(u'Prepare a statement: [{}]'.format(query)) # Send Parse message to server # We don't need to tell the server the parameter types yet self.connection.write(messages.Parse(self.prepared_name, query, param_types=())) # Send Describe message to server self.connection.write(messages.Describe('prepared_statement', self.prepared_name)) self.connection.write(messages.Flush()) # Read expected message: ParseComplete self._message = self.connection.read_expected_message(messages.ParseComplete, self._error_handler) # Read expected message: ParameterDescription self._message = self.connection.read_expected_message(messages.ParameterDescription, self._error_handler) self._param_metadata = self._message.parameters # Read expected message: RowDescription or NoData self._message = self.connection.read_expected_message( (messages.RowDescription, messages.NoData), self._error_handler) if isinstance(self._message, messages.NoData): self.description = None # response was NoData for a DDL/transaction PreparedStatement else: self.description = [Column(fd, self.unicode_error) for fd in self._message.fields] # Read expected message: CommandDescription self._message = self.connection.read_expected_message(messages.CommandDescription, self._error_handler) if len(self._message.command_tag) == 0: msg = 'The statement being prepared is empty' self._logger.error(msg) self.connection.write(messages.Sync()) raise errors.EmptyQueryError(msg) self._logger.info('Finish preparing the statement') def _execute_prepared_statement(self, list_of_parameter_values): """ Send multiple statement parameter sets to the server using the extended query protocol. The server would bind and execute each set of parameter values. This function should not be called without first calling _prepare() to prepare a statement. """ portal_name = "" parameter_type_oids = [metadata['data_type_oid'] for metadata in self._param_metadata] parameter_count = len(self._param_metadata) try: if len(list_of_parameter_values) == 0: raise ValueError("Empty list/tuple, nothing to execute") for parameter_values in list_of_parameter_values: if parameter_values is None: parameter_values = () self._logger.info(u'Bind parameters: {}'.format(parameter_values)) if len(parameter_values) != parameter_count: msg = ("Invalid number of parameters for {}: {} given, {} expected" .format(parameter_values, len(parameter_values), parameter_count)) raise ValueError(msg) self.connection.write(messages.Bind(portal_name, self.prepared_name, parameter_values, parameter_type_oids)) self.connection.write(messages.Execute(portal_name, 0)) self.connection.write(messages.Sync()) except Exception as e: self._logger.error(str(e)) # the server will not send anything until we issue a sync self.connection.write(messages.Sync()) self._message = self.connection.read_message() raise self.connection.write(messages.Flush()) # Read expected message: BindComplete self.connection.read_expected_message(messages.BindComplete) self._message = self.connection.read_message() if isinstance(self._message, messages.ErrorResponse): raise errors.QueryError.from_error_response(self._message, self.prepared_sql) def _close_prepared_statement(self): """ Close the prepared statement on the server. """ self.prepared_sql = None self.flush_to_query_ready() self.connection.write(messages.Close('prepared_statement', self.prepared_name)) self.connection.write(messages.Flush()) self._message = self.connection.read_expected_message(messages.CloseComplete) self.connection.write(messages.Sync()) vertica-python-0.10.1/vertica_python/vertica/log.py000066400000000000000000000060331357720411700224040ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import errno import os import logging class VerticaLogging(object): @classmethod def setup_logging(cls, logger_name, logfile, log_level=logging.INFO, context=''): logger = logging.getLogger(logger_name) logger.setLevel(log_level) if logfile: formatter = logging.Formatter( fmt=('%(asctime)s.%(msecs)03d [%(module)s] ' '{}/%(process)d:0x%(thread)x <%(levelname)s> ' '%(message)s'.format(context)), datefmt='%Y-%m-%d %H:%M:%S') cls.ensure_dir_exists(logfile) file_handler = logging.FileHandler(logfile, encoding='utf-8') file_handler.setFormatter(formatter) logger.addHandler(file_handler) @classmethod def ensure_dir_exists(cls, filepath): """Ensure that a directory exists If it doesn't exist, try to create it and protect against a race condition if another process is doing the same. """ directory = os.path.dirname(filepath) if directory != '' and not os.path.exists(directory): try: os.makedirs(directory) except OSError as e: if e.errno != errno.EEXIST: raise vertica-python-0.10.1/vertica_python/vertica/messages/000077500000000000000000000000001357720411700230565ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/vertica/messages/__init__.py000066400000000000000000000040151357720411700251670ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..messages import backend_messages from ..messages.backend_messages import * from ..messages import frontend_messages from ..messages.frontend_messages import * __all__ = backend_messages.__all__ + frontend_messages.__all__ vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/000077500000000000000000000000001357720411700263345ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/__init__.py000066400000000000000000000060461357720411700304530ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .authentication import Authentication from .backend_key_data import BackendKeyData from .bind_complete import BindComplete from .close_complete import CloseComplete from .command_complete import CommandComplete from .command_description import CommandDescription from .copy_in_response import CopyInResponse from .data_row import DataRow from .empty_query_response import EmptyQueryResponse from .error_response import ErrorResponse from .load_balance_response import LoadBalanceResponse from .no_data import NoData from .notice_response import NoticeResponse from .parameter_description import ParameterDescription from .parameter_status import ParameterStatus from .parse_complete import ParseComplete from .portal_suspended import PortalSuspended from .ready_for_query import ReadyForQuery from .row_description import RowDescription from .unknown import Unknown __all__ = ['RowDescription', 'ReadyForQuery', 'PortalSuspended', 'ParseComplete', 'ParameterStatus', 'ParameterDescription', 'NoticeResponse', 'NoData', 'LoadBalanceResponse', 'ErrorResponse', 'EmptyQueryResponse', 'DataRow', 'CopyInResponse', 'CommandDescription', 'CommandComplete', 'CloseComplete', 'BindComplete', 'BackendKeyData', 'Authentication', 'Unknown'] vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/authentication.py000066400000000000000000000063031357720411700317270ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import unpack from ..message import BackendMessage from .... import errors class Authentication(BackendMessage): message_id = b'R' OK = 0 KERBEROS_V4 = 1 KERBEROS_V5 = 2 CLEARTEXT_PASSWORD = 3 CRYPT_PASSWORD = 4 MD5_PASSWORD = 5 SCM_CREDENTIAL = 6 GSS = 7 GSS_CONTINUE = 8 CHANGE_PASSWORD = 9 PASSWORD_CHANGED = 10 # client doesn't do password changing, this should never be seen PASSWORD_GRACE = 11 HASH = 65536 HASH_MD5 = 65536 + 5 HASH_SHA512 = 65536 + 512 def __init__(self, data): BackendMessage.__init__(self) self.code, other = unpack('!I{0}s'.format(len(data) - 4), data) if self.code == self.CRYPT_PASSWORD: self.salt = other elif self.code in (self.MD5_PASSWORD, self.HASH_MD5): self.salt = other[:4] elif self.code in (self.HASH, self.HASH_SHA512): self.salt = other[:4] userSaltLen = unpack('!I', other[4:8])[0] if userSaltLen != 16: raise errors.MessageError( 'Received wrong user salt size: {}'.format(userSaltLen)) self.usersalt = unpack('!{0}s'.format(userSaltLen), other[8:])[0] elif self.code in [self.GSS_CONTINUE]: self.auth_data = other def __str__(self): return "Authentication: type={}".format(self.code) BackendMessage.register(Authentication) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/backend_key_data.py000066400000000000000000000043361357720411700321440ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import unpack from ..message import BackendMessage class BackendKeyData(BackendMessage): message_id = b'K' def __init__(self, data): BackendMessage.__init__(self) unpacked = unpack('!2I', data) self.pid = unpacked[0] self.key = unpacked[1] def __str__(self): return "BackendKeyData: pid = {}, key = {}".format(self.pid, self.key) BackendMessage.register(BackendKeyData) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/bind_complete.py000066400000000000000000000037631357720411700315230ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage class BindComplete(BackendMessage): message_id = b'2' def __init__(self, data): BackendMessage.__init__(self) BackendMessage.register(BindComplete) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/close_complete.py000066400000000000000000000037651357720411700317160ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage class CloseComplete(BackendMessage): message_id = b'3' def __init__(self, data): BackendMessage.__init__(self) BackendMessage.register(CloseComplete) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/command_complete.py000066400000000000000000000046171357720411700322240ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ CommandComplete message The server prompt that indicates a command has completed. The command tag string is the name of the command that was run. """ from __future__ import print_function, division, absolute_import import re from struct import unpack from ..message import BackendMessage class CommandComplete(BackendMessage): message_id = b'C' def __init__(self, data): BackendMessage.__init__(self) data = unpack('{0}sx'.format(len(data) - 1), data)[0] self.command_tag = data.decode('utf-8') def __str__(self): return 'CommandComplete: command_tag = "{}"'.format(self.command_tag) BackendMessage.register(CommandComplete) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/command_description.py000066400000000000000000000056761357720411700327450ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ CommandDescription message -- part of the response to a Describe request message. This response informs the client about the type of command being executed. If the command is a parameterized INSERT statement, the copy_rewrite field may include a semantically-equivalent COPY STDIN statement. Clients can choose to run this statement instead to achieve better performance when loading many batches of parameters. """ from __future__ import print_function, division, absolute_import from struct import unpack from ..message import BackendMessage class CommandDescription(BackendMessage): message_id = b'm' def __init__(self, data): BackendMessage.__init__(self) pos = data.find(b'\x00') unpacked = unpack("!{0}sxH{1}sx".format(pos, len(data) - pos - 4), data) self.command_tag = unpacked[0].decode('utf-8') self.has_copy_rewrite = (unpacked[1] == 1) self.copy_rewrite = unpacked[2].decode('utf-8') def __str__(self): return ('CommandDescription: command_tag = "{}", has_copy_rewrite = {},' ' copy_rewrite = "{}"'.format( self.command_tag, self.has_copy_rewrite, self.copy_rewrite)) BackendMessage.register(CommandDescription) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/copy_in_response.py000066400000000000000000000042411357720411700322650ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import unpack from ..message import BackendMessage class CopyInResponse(BackendMessage): message_id = b'G' def __init__(self, data): BackendMessage.__init__(self) values = unpack('!B{0}H'.format((len(data) - 1) // 2), data) self.format = values[0] self.column_formats = values[2::] BackendMessage.register(CopyInResponse) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/data_row.py000066400000000000000000000047631357720411700305200ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import unpack, unpack_from from six.moves import range from ..message import BackendMessage class DataRow(BackendMessage): message_id = b'D' def __init__(self, data): BackendMessage.__init__(self) self.values = [] field_count = unpack('!H', data[0:2])[0] pos = 2 for i in range(field_count): size = unpack_from('!I', data, pos)[0] if size == 4294967295: size = -1 if size == -1: self.values.append(None) else: self.values.append(unpack_from('{0}s'.format(size), data, pos + 4)[0]) pos += (4 + max(size, 0)) BackendMessage.register(DataRow) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/empty_query_response.py000066400000000000000000000037771357720411700332250ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage class EmptyQueryResponse(BackendMessage): message_id = b'I' def __init__(self, data): BackendMessage.__init__(self) BackendMessage.register(EmptyQueryResponse) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/error_response.py000066400000000000000000000041641357720411700317620ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage from vertica_python.vertica.messages.backend_messages.notice_response import NoticeResponse class ErrorResponse(NoticeResponse, BackendMessage): message_id = b'E' def __str__(self): return "ErrorResponse: {}".format(self.error_message()) BackendMessage.register(ErrorResponse) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/load_balance_response.py000066400000000000000000000045741357720411700332220ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage from struct import unpack class LoadBalanceResponse(BackendMessage): message_id = b'Y' def __init__(self, data): BackendMessage.__init__(self) unpacked = unpack('!I{0}sx'.format(len(data) - 5), data) self.port = unpacked[0] self.host = unpacked[1].decode('utf-8') def get_port(self): return self.port def get_host(self): return self.host def __str__(self): return "LoadBalanceResponse: host={}, port={}".format(self.host, self.port) BackendMessage.register(LoadBalanceResponse) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/no_data.py000066400000000000000000000037471357720411700303260ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage class NoData(BackendMessage): message_id = b'n' def __init__(self, data): BackendMessage.__init__(self) BackendMessage.register(NoData) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/notice_response.py000066400000000000000000000074771357720411700321240ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import six from struct import unpack_from from ...mixins.notice_response_attr import _NoticeResponseAttrMixin from ..message import BackendMessage class NoticeResponse(_NoticeResponseAttrMixin, BackendMessage): message_id = b'N' def __init__(self, data): BackendMessage.__init__(self) # `_notice_attrs` is required by _NoticeResponseAttrMixin and also used # by QueryError self._notice_attrs = NoticeResponse._unpack_data(data) def error_message(self): return ', '.join([ "{0}: {1}".format(name, value) for (name, value) in six.iteritems(self.values) ]) def __str__(self): return "NoticeResponse: {}".format(self.error_message()) @property def values(self): """ A mapping of server-provided values describing this notice. The keys of this mapping are user-facing strings. The contents of any given NoticeResponse can vary based on the context or version of Vertica. For access to specific values, the appropriate property getter is recommended. Example return value: ``` { 'Severity': 'ERROR', 'Message': 'Syntax error at or near "foobar"', 'Sqlstate': '42601', 'Position': '1', 'Routine': 'base_yyerror', 'File': '/data/.../vertica/Parser/scan.l', 'Line': '1043', 'Error Code': '4856' } ``` """ return self._get_labeled_values() @staticmethod def _unpack_data(data): data_mapping = {} pos = 0 while pos < len(data) - 1: null_byte = data.find(b'\x00', pos) unpacked = unpack_from('c{0}sx'.format(null_byte - 1 - pos), data, pos) key = unpacked[0] value = unpacked[1] data_mapping[key] = value.decode('utf-8') pos += (len(value) + 2) return data_mapping BackendMessage.register(NoticeResponse) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/parameter_description.py000066400000000000000000000067611357720411700333030ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import unpack, unpack_from, calcsize from ..message import BackendMessage from ....datatypes import getTypeName class ParameterDescription(BackendMessage): message_id = b't' def __init__(self, data): BackendMessage.__init__(self) self.parameters = [] self.parameter_count = unpack('!H', data[0:2])[0] if self.parameter_count == 0: return # read type pool # used for special types e.g. GEOMETRY, GEOGRAPHY user_types = [] type_pool_count = unpack('!I', data[2:6])[0] pos = 6 for _ in range(type_pool_count): base_type_oid = unpack('!I', data[pos:(pos + 4)])[0] pos += 4 type_name = unpack_from("!{0}sx".format(data.find(b'\x00', pos) - pos), data, pos)[0] pos += len(type_name) + 1 user_types.append((base_type_oid, type_name)) # read info of each parameter offset = calcsize("!BIiH") for _ in range(self.parameter_count): field_info = unpack_from("!BIiH", data, pos) pos += offset if field_info[0] == 1: data_type_oid, data_type_name = user_types[field_info[1]] else: data_type_oid = field_info[1] data_type_name = getTypeName(data_type_oid, field_info[2]) self.parameters.append({ 'data_type_oid': data_type_oid, 'data_type_name': data_type_name, 'type_modifier': field_info[2], 'null_ok': field_info[3] != 1, }) def __str__(self): return "ParameterDescription: {}".format(self.parameters) BackendMessage.register(ParameterDescription) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/parameter_status.py000066400000000000000000000057531357720411700323030ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ ParameterStatus message A ParameterStatus message will be generated whenever the backend believes the frontend should know about a setting parameter value. For example, when you do SET SESSION AUTOCOMMIT ON | OFF, you get back a parameter status telling you the new value of autocommit. At present Vertica supports a handful of parameters, they are: standard_conforming_strings, server_version, client_locale, client_label, long_string_types, protocol_version, auto_commit, MARS More parameters would be added in the future. Accordingly, a frontend should simply ignore ParameterStatus for parameters that it does not understand or care about. """ from __future__ import print_function, division, absolute_import from struct import unpack from ..message import BackendMessage class ParameterStatus(BackendMessage): message_id = b'S' def __init__(self, data): BackendMessage.__init__(self) null_byte = data.find(b'\x00') unpacked = unpack('{0}sx{1}sx'.format(null_byte, len(data) - null_byte - 2), data) self.name = unpacked[0].decode('utf-8') self.value = unpacked[1].decode('utf-8') def __str__(self): return "ParameterStatus: {} = {}".format(self.name, self.value) BackendMessage.register(ParameterStatus) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/parse_complete.py000066400000000000000000000037651357720411700317230ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage class ParseComplete(BackendMessage): message_id = b'1' def __init__(self, data): BackendMessage.__init__(self) BackendMessage.register(ParseComplete) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/portal_suspended.py000066400000000000000000000052101357720411700322570ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ PortalSuspended message A PortalSuspended message indicates that a portal has stopped execution. Vertica does not support portals in the same way postgres does. A portal is never truly "suspended" because Vertica always returns all results, regardless of how many were requested in a Bind message. This effectively means PortalSuspended has the same meaning as a CommandComplete message. The only meaningful difference being PortalSuspended occurs during the extended query protocol, while CommandComplete happens with the simple query protocol. In the future, Vertica may change to restore semantics more similar to those intended by Postgres. """ from __future__ import print_function, division, absolute_import from ..message import BackendMessage class PortalSuspended(BackendMessage): message_id = b's' def __init__(self, data): BackendMessage.__init__(self) BackendMessage.register(PortalSuspended) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/ready_for_query.py000066400000000000000000000047751357720411700321220ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ ReadyForQuery message The backend informs the frontend that it may safely send a new command. The ReadyForQuery message is the same one that the backend will issue after each command cycle. """ from __future__ import print_function, division, absolute_import from struct import unpack from ..message import BackendMessage class ReadyForQuery(BackendMessage): message_id = b'Z' STATUSES = { b'I': 'no_transaction', b'T': 'in_transaction', b'E': 'failed_transaction' } def __init__(self, data): BackendMessage.__init__(self) self.transaction_status = self.STATUSES[unpack('c', data)[0]] def __str__(self): return "ReadyForQuery: status = {}".format(self.transaction_status) BackendMessage.register(ReadyForQuery) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/row_description.py000066400000000000000000000113551357720411700321250ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ RowDescription message RowDescription message describes the column layout of the rows that will be returned in response to a SELECT, FETCH, etc query. """ from __future__ import print_function, division, absolute_import from struct import unpack, unpack_from, calcsize from six.moves import range from ..message import BackendMessage from ....datatypes import getTypeName class RowDescription(BackendMessage): message_id = b'T' def __init__(self, data): BackendMessage.__init__(self) self.fields = [] field_count = unpack('!H', data[0:2])[0] if field_count == 0: return # read type pool # used for special types e.g. GEOMETRY, GEOGRAPHY user_types = [] type_pool_count = unpack('!I', data[2:6])[0] pos = 6 for _ in range(type_pool_count): base_type_oid = unpack('!I', data[pos:(pos + 4)])[0] pos += 4 type_name = unpack_from("!{0}sx".format(data.find(b'\x00', pos) - pos), data, pos)[0] pos += len(type_name) + 1 user_types.append((base_type_oid, type_name.decode('utf-8'))) # read info of each field offset = calcsize("!HBIHHHiH") for _ in range(field_count): field_name = unpack_from("!{0}sx".format(data.find(b'\x00', pos) - pos), data, pos)[0] pos += len(field_name) + 1 field_name = field_name.decode('utf-8') table_oid = unpack('!Q', data[pos:(pos + 8)])[0] pos += 8 schema_name, table_name = None, None if table_oid != 0: schema_name = unpack_from("!{0}sx".format(data.find(b'\x00', pos) - pos), data, pos)[0] pos += len(schema_name) + 1 schema_name = schema_name.decode('utf-8') table_name = unpack_from("!{0}sx".format(data.find(b'\x00', pos) - pos), data, pos)[0] pos += len(table_name) + 1 table_name = table_name.decode('utf-8') field_info = unpack_from("!HBIHHHiH", data, pos) pos += offset if field_info[1] == 1: data_type_oid, data_type_name = user_types[field_info[2]] else: data_type_oid = field_info[2] data_type_name = getTypeName(data_type_oid, field_info[6]) self.fields.append({ 'name': field_name, 'table_oid': table_oid, 'schema_name': schema_name, 'table_name': table_name, 'attribute_number': field_info[0], 'data_type_oid': data_type_oid, 'data_type_size': field_info[3], 'data_type_name': data_type_name, 'null_ok': field_info[4] == 1, 'is_identity': field_info[5] == 1, 'type_modifier': field_info[6], 'format_code': field_info[7], }) def __str__(self): return "RowDescription: {}".format(self.fields) BackendMessage.register(RowDescription) vertica-python-0.10.1/vertica_python/vertica/messages/backend_messages/unknown.py000066400000000000000000000041031357720411700304030ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BackendMessage class Unknown(BackendMessage): def __init__(self, message_id, data): BackendMessage.__init__(self) self._message_id = message_id self.data = data @property def message_id(self): return self._message_id vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/000077500000000000000000000000001357720411700265645ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/__init__.py000066400000000000000000000051201357720411700306730ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from .bind import Bind from .cancel_request import CancelRequest from .close import Close from .copy_data import CopyData from .copy_stream import CopyStream from .copy_done import CopyDone from .copy_fail import CopyFail from .describe import Describe from .execute import Execute from .flush import Flush from .load_balance_request import LoadBalanceRequest from .parse import Parse from .password import Password from .query import Query from .ssl_request import SslRequest from .startup import Startup from .sync import Sync from .terminate import Terminate __all__ = ['Bind', 'Query', 'CancelRequest', 'Close', 'CopyData', 'CopyDone', 'CopyFail', 'CopyStream', 'Describe', 'Execute', 'Flush', 'LoadBalanceRequest', 'Parse', 'Password', 'SslRequest', 'Startup', 'Sync', 'Terminate'] vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/bind.py000066400000000000000000000107701357720411700300570ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Bind message In the extended query protocol, the frontend sends a Bind message to bind values to parameter placeholders present in an existing prepared statement. The response is either BindComplete or ErrorResponse. """ from __future__ import print_function, division, absolute_import from struct import pack from six import string_types from ..message import BulkFrontendMessage from ....datatypes import VerticaType from ....compat import as_bytes BACKSLASH = b'\\' BACKSLASH_ESCAPE = b'\\134' class Bind(BulkFrontendMessage): message_id = b'B' def __init__(self, portal_name, prepared_statement_name, parameter_values, parameter_type_oids): BulkFrontendMessage.__init__(self) self._portal_name = portal_name self._prepared_statement_name = prepared_statement_name self._parameter_values = parameter_values self._parameter_type_oids = parameter_type_oids def read_bytes(self): utf_portal_name = self._portal_name.encode('utf-8') utf_prepared_statement_name = self._prepared_statement_name.encode('utf-8') bytes_ = pack('!{0}sx{1}sx'.format(len(utf_portal_name), len(utf_prepared_statement_name)), utf_portal_name, utf_prepared_statement_name) # Parameter format codes -- use the default format (text) bytes_ += pack('!H', 0) # Number of parameters bytes_ += pack('!H', len(self._parameter_type_oids)) param_bytes_ = b'' for oid, val in zip(self._parameter_type_oids, self._parameter_values): # Parameter type oids bytes_ += pack('!I', oid) # Parameter values if val is None: # -1 indicates a NULL parameter value param_bytes_ += pack('!i', -1) elif oid in (VerticaType.BINARY, VerticaType.VARBINARY, VerticaType.LONGVARBINARY): # Encode binary data as UTF8 bytes val = as_bytes(val) # Escape the byte value \ with "\134"(octal for backslash) val = val.replace(BACKSLASH, BACKSLASH_ESCAPE) param_bytes_ += pack('!I{0}s'.format(len(val)), len(val), val) else: # Convert input to string if oid == VerticaType.BOOL: val = '1' if str(val).lower() in ('t', 'true', 'y', 'yes', '1') else '0' elif not isinstance(val, (string_types, bytes)): val = str(val) # Encode string as UTF8 bytes val = val.encode('utf-8') if not isinstance(val, bytes) else val param_bytes_ += pack('!I{0}s'.format(len(val)), len(val), val) bytes_ += param_bytes_ # Result column format codes -- use the default format (text) bytes_ += pack('!H', 0) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/cancel_request.py000066400000000000000000000055171357720411700321430ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ CancelRequest message The frontend sends a CancelRequest message to cancel the processing of the current operation. The cancel request must be sent across a new connection to the server. The server will process this request and then close the connection. The cancel request might or might not have any effect. If the cancellation is effective, the current command will terminate early and return an error message. If the cancellation fails (e.g. the server has finished processing the command), then there will be no visible result at all. """ from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class CancelRequest(BulkFrontendMessage): message_id = None def __init__(self, backend_pid, backend_key): BulkFrontendMessage.__init__(self) self._backend_pid = backend_pid # The process ID of the target backend self._backend_key = backend_key # The secret key of the target backend def read_bytes(self): bytes_ = pack('!3I', 80877102, self._backend_pid, self._backend_key) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/close.py000066400000000000000000000055451357720411700302540ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Close message In the extended query protocol, the frontend sends a Close message to close an existing prepared statement or portal and release resources. The response is either CloseComplete or ErrorResponse. It is not an error to issue Close against a nonexistent statement or portal name. """ from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class Close(BulkFrontendMessage): message_id = b'C' def __init__(self, close_type, close_name): BulkFrontendMessage.__init__(self) self._close_name = close_name if close_type == 'portal': self._close_type = b'P' elif close_type == 'prepared_statement': self._close_type = b'S' else: raise ValueError("{0} is not a valid close_type. " "Must be either portal or prepared_statement".format(close_type)) def read_bytes(self): utf_close_name = self._close_name.encode('utf-8') bytes_ = pack('c{0}sx'.format(len(utf_close_name)), self._close_type, utf_close_name) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/copy_data.py000066400000000000000000000047101357720411700311030ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from six import text_type, binary_type from ..message import BulkFrontendMessage class CopyData(BulkFrontendMessage): message_id = b'd' def __init__(self, data, unicode_error='strict'): BulkFrontendMessage.__init__(self) self._unicode_error = unicode_error if isinstance(data, text_type): self._data = self._data.encode(encoding='utf-8', errors=self._unicode_error) elif isinstance(data, binary_type): self._data = data else: raise TypeError("should be string or bytes") def read_bytes(self): # to deal with unicode strings bytes_ = self._data return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/copy_done.py000066400000000000000000000036141357720411700311210ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BulkFrontendMessage class CopyDone(BulkFrontendMessage): message_id = b'c' vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/copy_fail.py000066400000000000000000000042521357720411700311060ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class CopyFail(BulkFrontendMessage): message_id = b'f' def __init__(self, error_message): BulkFrontendMessage.__init__(self) self._error_message = error_message def read_bytes(self): bytes_ = pack('{0}sx'.format(len(self._error_message)), self._error_message) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/copy_stream.py000066400000000000000000000052461357720411700314720ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from six import text_type, binary_type from ..message import StreamFrontendMessage DEFAULT_BUFFER_SIZE = 131072 class CopyStream(StreamFrontendMessage): message_id = b'd' def __init__(self, stream, buffer_size=DEFAULT_BUFFER_SIZE, unicode_error='strict'): StreamFrontendMessage.__init__(self) self._stream = stream self._unicode_error = unicode_error self._buffer_size = buffer_size def stream_bytes(self): while True: chunk = self._stream.read(self._buffer_size) if isinstance(chunk, text_type): bytes_ = chunk.encode(encoding='utf-8', errors=self._unicode_error) elif isinstance(chunk, binary_type): bytes_ = chunk else: raise TypeError("should be string or bytes") if not chunk: break yield bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/crypt_windows.py000077500000000000000000000241201357720411700320530ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from six.moves import range # Initial permutation IP = ( 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, ) # Final permutation, FP = IP^(-1) FP = ( 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, ) # Permuted-choice 1 from the key bits to yield C and D. # Note that bits 8,16... are left out: They are intended for a parity check. PC1_C = ( 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, ) PC1_D = ( 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4, ) # Permuted-choice 2, to pick out the bits from the CD array that generate the # key schedule. PC2_C = ( 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, ) PC2_D = ( 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32, ) # The C and D arrays are used to calculate the key schedule. C = [0] * 28 D = [0] * 28 # The key schedule. Generated from the key. KS = [[0] * 48 for _ in range(16)] # The E bit-selection table. E = [0] * 48 e2 = ( 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1, ) # S-boxes. S = ( ( 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 ), ( 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 ), ( 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 ), ( 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 ), ( 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 ), ( 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 ), ( 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 ), ( 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 ) ) # P is a permutation on the selected combination of the current L and key. P = ( 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25, ) # The combination of the key and the input, before selection. preS = [0] * 48 def __setkey(key): """ Set up the key schedule from the encryption key. """ global C, D, KS, E shifts = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1) # First, generate C and D by permuting the key. The lower order bit of each # 8-bit char is not used, so C and D are only 28 bits apiece. for i in range(28): C[i] = key[PC1_C[i] - 1] D[i] = key[PC1_D[i] - 1] for i in range(16): # rotate for k in range(shifts[i]): temp = C[0] for j in range(27): C[j] = C[j + 1] C[27] = temp temp = D[0] for j in range(27): D[j] = D[j + 1] D[27] = temp # get Ki. Note C and D are concatenated for j in range(24): KS[i][j] = C[PC2_C[j] - 1] KS[i][j + 24] = D[PC2_D[j] - 28 - 1] # load E with the initial E bit selections for i in range(48): E[i] = e2[i] def __encrypt(block): global preS left, right = [], [] # block in two halves f = [0] * 32 # First, permute the bits in the input for j in range(32): left.append(block[IP[j] - 1]) for j in range(32, 64): right.append(block[IP[j] - 1]) # Perform an encryption operation 16 times. for i in range(16): # Save the right array, which will be the new left. old = right[:] # Expand right to 48 bits using the E selector and exclusive-or with # the current key bits. for j in range(48): preS[j] = right[E[j] - 1] ^ KS[i][j] # The pre-select bits are now considered in 8 groups of 6 bits each. # The 8 selection functions map these 6-bit quantities into 4-bit # quantities and the results are permuted to make an f(R, K). # The indexing into the selection functions is peculiar; it could be # simplified by rewriting the tables. for j in range(8): temp = 6 * j k = S[j][(preS[temp + 0] << 5) + (preS[temp + 1] << 3) + (preS[temp + 2] << 2) + (preS[temp + 3] << 1) + (preS[temp + 4] << 0) + (preS[temp + 5] << 4)] temp = 4 * j f[temp + 0] = (k >> 3) & 1 f[temp + 1] = (k >> 2) & 1 f[temp + 2] = (k >> 1) & 1 f[temp + 3] = (k >> 0) & 1 # The new right is left ^ f(R, K). # The f here has to be permuted first, though. for j in range(32): right[j] = left[j] ^ f[P[j] - 1] # Finally the new left (the original right) is copied back. left = old # The output left and right are reversed. left, right = right, left # The final output gets the inverse permutation of the very original for j in range(64): i = FP[j] if i < 33: block[j] = left[i - 1] else: block[j] = right[i - 33] return block def crypt(pw, salt): iobuf = [] # break pw into 64 bits block = [] for c in pw: c = ord(c) for j in range(7): block.append((c >> (6 - j)) & 1) block.append(0) block += [0] * (64 - len(block)) # set key based on pw __setkey(block) for i in range(2): # store salt at beginning of results iobuf.append(salt[i]) c = ord(salt[i]) if c > ord('Z'): c -= 6 if c > ord('9'): c -= 7 c -= ord('.') # use salt to effect the E-bit selection for j in range(6): if (c >> j) & 1: E[6 * i + j], E[6 * i + j + 24] = E[6 * i + j + 24], E[6 * i + j] # call DES encryption 25 times using pw as key and initial data = 0 block = [0] * 66 for i in range(25): block = __encrypt(block) # format encrypted block for standard crypt(3) output for i in range(11): c = 0 for j in range(6): c <<= 1 c |= block[6 * i + j] c += ord('.') if c > ord('9'): c += 7 if c > ord('Z'): c += 6 iobuf.append(chr(c)) return ''.join(iobuf) vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/describe.py000066400000000000000000000062451357720411700307250ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Describe message In the extended query protocol, the frontend sends a Describe message, which specifies the name of an existing prepared statement. The first response is a ParameterDescription message describing the parameters needed by the statement. The second response is a RowDescription message describing the rows that will be returned when the statement is eventually executed (or a NoData message if the statement will not return rows). The third response is a CommandDescription message describing the type of command to be executed and any semantically-equivalent COPY statement. """ from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class Describe(BulkFrontendMessage): message_id = b'D' def __init__(self, describe_type, describe_name): BulkFrontendMessage.__init__(self) self._describe_name = describe_name if describe_type == 'portal': self._describe_type = b'P' elif describe_type == 'prepared_statement': self._describe_type = b'S' else: raise ValueError("{0} is not a valid describe_type. " "Must be either portal or prepared_statement".format(describe_type)) def read_bytes(self): utf_name = self._describe_name.encode('utf-8') bytes_ = pack('c{0}sx'.format(len(utf_name)), self._describe_type, utf_name) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/execute.py000066400000000000000000000054321357720411700306040ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Execute message In the extended query protocol, the frontend sends a Execute message once a portal exists. Execute doesn't cause RowDescription response message to be issued, so the frontend should issue Describe before issuing Execute, to ensure that it knows how to interpret the result rows it will get back. The Execute message specifies the portal name and a maximum result-row count. Currently, Vertica backend will ignore this result-row count and send all the rows regardless of what you put here. """ from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class Execute(BulkFrontendMessage): message_id = b'E' def __init__(self, portal_name, max_rows): BulkFrontendMessage.__init__(self) self._portal_name = portal_name self._max_rows = max_rows def read_bytes(self): utf_portal_name = self._portal_name.encode('utf-8') bytes_ = pack('!{0}sxI'.format(len(utf_portal_name)), utf_portal_name, self._max_rows) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/flush.py000066400000000000000000000043751357720411700302700ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Flush message The Flush message does not cause any specific output to be generated, but forces the backend to deliver any data pending in its output buffers. For example, in the extended query protocol, a Flush must be sent after any extended-query command except Sync, if the frontend wishes to examine the results of that command before issuing more commands. """ from __future__ import print_function, division, absolute_import from ..message import BulkFrontendMessage class Flush(BulkFrontendMessage): message_id = b'H' vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/load_balance_request.py000077500000000000000000000040711357720411700332770ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class LoadBalanceRequest(BulkFrontendMessage): message_id = None LOADBALANCE_REQUEST = 80936960 def read_bytes(self): bytes_ = pack('!I', self.LOADBALANCE_REQUEST) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/parse.py000066400000000000000000000057021357720411700302540ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Parse message In the extended query protocol, the frontend first sends a Parse message, which contains a textual query string. The query string leaves certain values unspecified with parameter placeholders (i.e. question mark '?'). The response is either ParseComplete or ErrorResponse. The query string cannot include more than one SQL statement; else an ErrorResponse is reported. The error message would be something like "Cannot insert multiple commands into a prepared statement" """ from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class Parse(BulkFrontendMessage): message_id = b'P' def __init__(self, name, query, param_types): BulkFrontendMessage.__init__(self) self._name = name self._query = query self._param_types = param_types def read_bytes(self): utf_name = self._name.encode('utf-8') utf_query = self._query.encode('utf-8') bytes_ = pack('!{0}sx{1}sxH'.format(len(utf_name), len(utf_query)), utf_name, utf_query, len(self._param_types)) for param in self._param_types: bytes_ += pack('!I', param) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/password.py000066400000000000000000000103741357720411700310050ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import import os import hashlib from struct import pack from ..message import BulkFrontendMessage from ..backend_messages.authentication import Authentication from ....compat import as_bytes if os.name == 'nt': from . import crypt_windows as crypt else: import crypt class Password(BulkFrontendMessage): message_id = b'p' def __init__(self, password, auth_method=None, options=None): BulkFrontendMessage.__init__(self) self._password = as_bytes(password) self._options = options or {} if auth_method is not None: self._auth_method = auth_method else: self._auth_method = Authentication.CLEARTEXT_PASSWORD def encoded_password(self): if self._auth_method == Authentication.CLEARTEXT_PASSWORD: return self._password elif self._auth_method == Authentication.CRYPT_PASSWORD: return crypt.crypt(self._password, self._options['salt']) elif self._auth_method in (Authentication.MD5_PASSWORD, Authentication.HASH, Authentication.HASH_MD5, Authentication.HASH_SHA512): # Encodes user/password/salt information in the following way: # MD5(MD5(password + user) + salt) # SHA512(SHA512(password + userSalt) + salt) useMD5 = self._auth_method in (Authentication.MD5_PASSWORD, Authentication.HASH_MD5) user = self._options['user'].encode('utf-8') if useMD5 else self._options['usersalt'] for key in (user, self._options['salt']): m = hashlib.md5() if useMD5 else hashlib.sha512() m.update(self._password + key) hexdigest = m.hexdigest() self._password = hexdigest.encode('utf-8') prefix = b'md5' if useMD5 else b'sha512' return prefix + self._password elif self._auth_method == Authentication.GSS: return self._password else: raise ValueError("unsupported authentication method: {0}".format(self._auth_method)) def read_bytes(self): encoded_pw = self.encoded_password() # Vertica server handles GSS messages differently from other passwords if self._auth_method == Authentication.GSS: bytes_ = pack('{0}s'.format(len(encoded_pw)), encoded_pw) else: bytes_ = pack('{0}sx'.format(len(encoded_pw)), encoded_pw) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/query.py000066400000000000000000000047641357720411700303160ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Query message In the simple query protocol, the frontend sends a Query message, which contains an SQL command (or commands) expressed as a text string. The backend then sends one or more response messages depending on the contents of the query command string, and finally a ReadyForQuery message. """ from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class Query(BulkFrontendMessage): message_id = b'Q' def __init__(self, query_string): BulkFrontendMessage.__init__(self) self._query_string = query_string def read_bytes(self): encoded = self._query_string.encode('utf-8') bytes_ = pack('{0}sx'.format(len(encoded)), encoded) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/ssl_request.py000066400000000000000000000040411357720411700315060ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from struct import pack from ..message import BulkFrontendMessage class SslRequest(BulkFrontendMessage): message_id = None SSL_REQUEST = 80877103 def read_bytes(self): bytes_ = pack('!I', self.SSL_REQUEST) return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/startup.py000066400000000000000000000077571357720411700306600ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Startup message To begin a session, the frontend opens a connection to the backend and sends a Startup message. """ from __future__ import print_function, division, absolute_import import platform import os from struct import pack # noinspection PyUnresolvedReferences,PyCompatibility import vertica_python from ..message import BulkFrontendMessage class Startup(BulkFrontendMessage): message_id = None def __init__(self, user, database, session_label, os_user_name): BulkFrontendMessage.__init__(self) try: os_platform = platform.platform() except Exception as e: os_platform = '' print("WARN: Cannot get the OS info: {}".format(str(e))) try: pid = str(os.getpid()) except Exception as e: pid = '0' print("WARN: Cannot get the process ID: {}".format(str(e))) self.parameters = { b'user': user, b'database': database, b'client_label': session_label, b'client_type': 'vertica-python', b'client_version': vertica_python.__version__, b'client_os': os_platform, b'client_os_user_name': os_user_name, b'client_pid': pid, } def read_bytes(self): # The fixed protocol version is followed by pairs of parameter name and value strings. # A zero byte is required as a terminator after the last name/value pair. # Parameters can appear in any order. fixed_protocol_version = 3 << 16 | 5 bytes_ = pack('!I', fixed_protocol_version) # The frontend sends a requested protocol version to the backend. # Old servers (protocol < 3.7) ignore this value and use the fixed protocol version. # New servers (protocol >= 3.7) would try to find the common protocol # version in use for both client and server, and send back a ParameterStatus # message (key='protocol_version', value=) bytes_ += pack('!16sxIx', b'protocol_version', vertica_python.PROTOCOL_VERSION) for k in self.parameters: v = self.parameters[k].encode('utf-8') bytes_ += pack('!{}sx{}sx'.format(len(k), len(v)), k, v) bytes_ += pack('x') return bytes_ vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/sync.py000066400000000000000000000036101357720411700301120ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BulkFrontendMessage class Sync(BulkFrontendMessage): message_id = b'S' vertica-python-0.10.1/vertica_python/vertica/messages/frontend_messages/terminate.py000066400000000000000000000036151357720411700311330ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function, division, absolute_import from ..message import BulkFrontendMessage class Terminate(BulkFrontendMessage): message_id = b'X' vertica-python-0.10.1/vertica_python/vertica/messages/message.py000066400000000000000000000121061357720411700250540ustar00rootroot00000000000000# Copyright (c) 2018-2019 Micro Focus or one of its affiliates. # Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Copyright (c) 2013-2017 Uber Technologies, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Vertica Frontend/Backend Protocol All communication between frontend (client) and backend (server) is through a stream of messages. Message Format: The first byte of a message identifies the message type, and the next four bytes specify the length of the rest of the message (this length count includes itself, but not the message-type byte). The remaining contents of the message are determined by the message type. An exception is the Startup message sent by frontend, which has no message-type byte. All data in Vertica is represented as UTF-8, so the frontend has to convert data going to the backend from Python text string into UTF-8, and to convert data coming from the backend from UTF-8 into Python text string. """ from __future__ import print_function, division, absolute_import from abc import ABCMeta from struct import pack from ..messages import * class Message(object): __metaclass__ = ABCMeta def __init__(self): pass @property def message_id(self): raise NotImplementedError("no default message_id") def _bytes_to_message(self, msg): if isinstance(msg, list): msg = ''.join(msg) if hasattr(msg, 'bytesize'): bytesize = msg.bytesize + 4 else: bytesize = len(msg) + 4 message_size = pack('!I', bytesize) if self.message_id is not None: msg_with_size = self.message_id + message_size + msg else: msg_with_size = message_size + msg return msg_with_size def __str__(self): return self.__class__.__name__ # noinspection PyAbstractClass class BackendMessage(Message): __metaclass__ = ABCMeta _message_id_map = {} @classmethod def from_type(cls, type_, data): klass = cls._message_id_map.get(type_) if klass is not None: return klass(data) else: from .backend_messages import Unknown return Unknown(type_, data) @staticmethod def register(cls): # TODO replace _message_id() with that assert issubclass(cls, BackendMessage), \ "{0} is not subclass of BackendMessage".format(cls.__name__) assert cls.message_id not in BackendMessage._message_id_map, \ "can't write the same key twice: {0}".format(cls.message_id) BackendMessage._message_id_map[cls.message_id] = cls # noinspection PyAbstractClass class FrontendMessage(Message): __metaclass__ = ABCMeta def fetch_message(self): """Generator for getting the message's content""" raise NotImplementedError("fetch_bytes has no default implementation") # noinspection PyAbstractClass class BulkFrontendMessage(FrontendMessage): __metaclass__ = ABCMeta def read_bytes(self): return b'' def get_message(self): bytes_ = self.read_bytes() return self._bytes_to_message(bytes_) def fetch_message(self): yield self.get_message() # noinspection PyAbstractClass class StreamFrontendMessage(FrontendMessage): __metaclass__ = ABCMeta def stream_bytes(self): raise NotImplementedError("stream_bytes has no default implementation") def stream_message(self): for bytes_ in self.stream_bytes(): yield self._bytes_to_message(bytes_) def fetch_message(self): for message in self.stream_message(): yield message vertica-python-0.10.1/vertica_python/vertica/mixins/000077500000000000000000000000001357720411700225565ustar00rootroot00000000000000vertica-python-0.10.1/vertica_python/vertica/mixins/__init__.py000066400000000000000000000012361357720411700246710ustar00rootroot00000000000000# Copyright (c) 2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import vertica-python-0.10.1/vertica_python/vertica/mixins/notice_response_attr.py000066400000000000000000000067321357720411700273710ustar00rootroot00000000000000# Copyright (c) 2019 Micro Focus or one of its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import from collections import OrderedDict FIELD_DEFINITIONS = [ {'type': b'q', 'name': "Internal Query", 'attribute': 'internal_query'}, {'type': b'S', 'name': "Severity", 'attribute': 'severity'}, {'type': b'M', 'name': "Message", 'attribute': 'message'}, {'type': b'C', 'name': "Sqlstate", 'attribute': 'sqlstate'}, {'type': b'D', 'name': "Detail", 'attribute': 'detail'}, {'type': b'H', 'name': "Hint", 'attribute': 'hint'}, {'type': b'P', 'name': "Position", 'attribute': 'position'}, {'type': b'W', 'name': "Where", 'attribute': 'where'}, {'type': b'p', 'name': "Internal Position", 'attribute': 'internal_position'}, {'type': b'R', 'name': "Routine", 'attribute': 'routine'}, {'type': b'F', 'name': "File", 'attribute': 'file'}, {'type': b'L', 'name': "Line", 'attribute': 'line'}, {'type': b'V', 'name': "Error Code", 'attribute': 'error_code'} ] FIELD_ATTR_TO_TYPE = {field['attribute']: field['type'] for field in FIELD_DEFINITIONS} class _NoticeResponseAttrMixin: # class must have `self._notice_attrs` property that provides a mapping from # the type indicator (see `FIELD_DEFINITIONS`) to value. @property def internal_query(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['internal_query']) @property def severity(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['severity']) @property def message(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['message']) @property def sqlstate(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['sqlstate']) @property def detail(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['detail']) @property def hint(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['hint']) @property def position(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['position']) @property def where(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['where']) @property def internal_position(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['internal_position']) @property def routine(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['routine']) @property def file(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['file']) @property def line(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['line']) @property def error_code(self): return self._notice_attrs.get(FIELD_ATTR_TO_TYPE['error_code']) def _get_labeled_values(self): values_mapping = OrderedDict() for field_def in FIELD_DEFINITIONS: if field_def['type'] in self._notice_attrs: values_mapping[field_def['name']] = self._notice_attrs[field_def['type']] return values_mapping