├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── announcements.md │ ├── bug_report.md │ ├── config.yml │ ├── documentation-and-example-improvements.md │ ├── enhancement-requests.md │ ├── general-questions-and-runtime-problems.md │ └── installation-questions.md ├── SUPPORT.md ├── pull_request_template.md └── stale.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── README.txt ├── SECURITY.md ├── doc ├── Makefile ├── README.md ├── requirements.txt └── src │ ├── api_manual │ ├── aq.rst │ ├── connection.rst │ ├── cursor.rst │ ├── deprecations.rst │ ├── lob.rst │ ├── module.rst │ ├── object_type.rst │ ├── session_pool.rst │ ├── soda.rst │ ├── subscription.rst │ └── variable.rst │ ├── conf.py │ ├── index.rst │ ├── license.rst │ ├── note.rst │ ├── release_notes.rst │ └── user_guide │ ├── aq.rst │ ├── batch_statement.rst │ ├── bind.rst │ ├── connection_handling.rst │ ├── cqn.rst │ ├── exception_handling.rst │ ├── globalization.rst │ ├── ha.rst │ ├── initialization.rst │ ├── installation.rst │ ├── introduction.rst │ ├── json_data_type.rst │ ├── lob_data.rst │ ├── plsql_execution.rst │ ├── soda.rst │ ├── sql_execution.rst │ ├── startup.rst │ ├── tracing_sql.rst │ ├── tuning.rst │ ├── txn_management.rst │ └── xml_data_type.rst ├── pyproject.toml ├── samples └── README.md ├── setup.cfg ├── setup.py ├── src ├── cxoApiType.c ├── cxoBuffer.c ├── cxoConnection.c ├── cxoCursor.c ├── cxoDbType.c ├── cxoDeqOptions.c ├── cxoEnqOptions.c ├── cxoError.c ├── cxoFuture.c ├── cxoJsonBuffer.c ├── cxoLob.c ├── cxoModule.c ├── cxoModule.h ├── cxoMsgProps.c ├── cxoObject.c ├── cxoObjectAttr.c ├── cxoObjectType.c ├── cxoQueue.c ├── cxoSessionPool.c ├── cxoSodaCollection.c ├── cxoSodaDatabase.c ├── cxoSodaDoc.c ├── cxoSodaDocCursor.c ├── cxoSodaOperation.c ├── cxoSubscr.c ├── cxoTransform.c ├── cxoUtils.c └── cxoVar.c ├── test ├── README.md ├── drop_test.py ├── setup_test.py ├── sql │ ├── drop_test.sql │ ├── drop_test_exec.sql │ ├── setup_test.sql │ └── setup_test_exec.sql ├── test_1000_module.py ├── test_1100_connection.py ├── test_1200_cursor.py ├── test_1300_cursor_var.py ├── test_1400_datetime_var.py ├── test_1500_types.py ├── test_1600_dml_returning.py ├── test_1700_error.py ├── test_1800_interval_var.py ├── test_1900_lob_var.py ├── test_2000_long_var.py ├── test_2100_nchar_var.py ├── test_2200_number_var.py ├── test_2300_object_var.py ├── test_2400_session_pool.py ├── test_2500_string_var.py ├── test_2600_timestamp_var.py ├── test_2700_aq.py ├── test_2800_bulk_aq.py ├── test_2900_rowid.py ├── test_3000_subscription.py ├── test_3100_boolean_var.py ├── test_3200_features_12_1.py ├── test_3300_soda_database.py ├── test_3400_soda_collection.py ├── test_3500_json.py ├── test_3600_outputtypehandler.py ├── test_3700_var.py ├── test_3800_typehandler.py └── test_env.py └── tox.ini /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.c text 4 | *.h text 5 | *.in text 6 | *.py text 7 | *.rst text 8 | *.txt text 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/announcements.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Announcements 3 | about: Use this if you are sharing something interesting 4 | title: '' 5 | labels: announcement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 31 | 32 | 1. What versions are you using? 33 | 34 | 55 | 56 | 2. Is it an error or a hang or a crash? 57 | 58 | 3. What error(s) or behavior you are seeing? 59 | 60 | 67 | 68 | 4. Include a runnable Python script that shows the problem. 69 | 70 | 81 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-and-example-improvements.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation and Example Improvements 3 | about: Use this to suggest changes to documentation and examples 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-requests.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Requests 3 | about: Use this for enhancement requests 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-questions-and-runtime-problems.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Questions and Runtime Problems 3 | about: For general cx_Oracle questions 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 30 | 31 | 1. What versions are you using? 32 | 33 | 53 | 54 | 2. Describe the problem 55 | 56 | 57 | 58 | 3. Include a runnable Python script that shows the problem. 59 | 60 | 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/installation-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Installation Problems 3 | about: Use this for cx_Oracle installation questions 4 | title: '' 5 | labels: install & configuration 6 | assignees: '' 7 | 8 | --- 9 | 10 | 38 | 39 | 1. What versions are you using? 40 | 41 | 55 | 56 | 2. Describe the problem 57 | 58 | 59 | 60 | 3. Show the directory listing where your Oracle Client libraries are installed (e.g. the Instant Client directory). Is it 64-bit or 32-bit? 61 | 62 | 4. Show what the `PATH` environment variable (on Windows) or `LD_LIBRARY_PATH` (on Linux) is set to? 63 | 64 | 5. Show any Oracle environment variables set (e.g. ORACLE_HOME, ORACLE_BASE). 65 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Python cx_Oracle Support 2 | 3 | **The cx_Oracle driver was renamed to python-oracledb in May 2022. It has a 4 | new repository at https://github.com/oracle/python-oracledb. Please update to 5 | this new driver. If you still have problems, open an issue on the new 6 | repository.** 7 | 8 | ## cx_Oracle Installation issues 9 | 10 | Read the [Installation instructions](http://cx-oracle.readthedocs.io/en/latest/installation.html) 11 | 12 | ## SQL and PL/SQL Questions 13 | 14 | Ask SQL and PL/SQL questions at [AskTOM](https://asktom.oracle.com/) 15 | 16 | Try out SQL and find code snippets on our hosted database 17 | with [LIVE SQL](https://livesql.oracle.com/) 18 | 19 | ## Database and other Oracle Issues 20 | 21 | Ask Database and other Oracle issues on 22 | an [OTN Forum](https://community.oracle.com/community/database/) 23 | 24 | ## cx_Oracle Documentation 25 | 26 | The cx_Oracle documentation 27 | is [here](http://cx-oracle.readthedocs.io/en/latest/) 28 | 29 | ## Got a cx_Oracle question? 30 | 31 | Ask at [GitHub](https://github.com/oracle/python-cx_Oracle/issues) 32 | 33 | When opening a new issue, fill in the template that will be shown. 34 | Include enough information for people to understand your problem. 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Thanks for contributing! 2 | 3 | The cx_Oracle driver was renamed to python-oracledb in May 2022. It has a new 4 | repository at https://github.com/oracle/python-oracledb. 5 | 6 | Please submit your contributions to the python-oracledb repository. 7 | 8 | No further releases under the cx_Oracle namespace are planned. 9 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # https://probot.github.io/apps/stale/ 2 | 3 | # Number of days of inactivity before an issue becomes stale 4 | daysUntilStale: 30 5 | # Number of days of inactivity before a stale issue is closed 6 | daysUntilClose: 7 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - enhancement 11 | - bug 12 | - announcement 13 | - OCA accepted 14 | # Label to use when marking an issue as stale 15 | staleLabel: inactive 16 | # Comment to post when marking an issue as stale. Set to `false` to disable 17 | markComment: > 18 | This issue has been automatically marked as inactive because it has not been 19 | updated recently. It will be closed if no further activity occurs. Thank you 20 | for your contributions. 21 | # Comment to post when closing a stale issue. Set to `false` to disable 22 | closeComment: > 23 | This issue has been automatically closed because it has not been updated for a month. 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .tox/ 3 | build/ 4 | dist/ 5 | doc/build 6 | cx_Oracle.egg-info/ 7 | MANIFEST 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "odpi"] 2 | path = odpi 3 | url = ../odpi.git 4 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # required 2 | version: 2 3 | 4 | build: 5 | os: ubuntu-20.04 6 | tools: 7 | python: "3.9" 8 | 9 | # Build documentation in the doc/src directory with Sphinx 10 | sphinx: 11 | configuration: doc/src/conf.py 12 | 13 | # declare Python requirements required to build docs 14 | python: 15 | install: 16 | - requirements: doc/requirements.txt 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The cx_Oracle driver was renamed to python-oracledb in May 2022. It has a new 4 | repository at https://github.com/oracle/python-oracledb 5 | 6 | Please submit your contributions to the python-oracledb repository. 7 | 8 | No further releases under the cx_Oracle namespace are planned. 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | LICENSE AGREEMENT FOR CX_ORACLE 2 | 3 | Copyright 2016, 2018, Oracle and/or its affiliates. All rights reserved. 4 | 5 | Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 6 | 7 | Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 8 | Canada. All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions, and the disclaimer that follows. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions, and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | 3. Neither the names of the copyright holders nor the names of any contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | *AS IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 26 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 31 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | Computronix is a registered trademark of Computronix (Canada) Ltd. 36 | 37 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include *.txt 3 | recursive-include odpi *.c 4 | recursive-include odpi *.h 5 | prune odpi/test 6 | prune odpi/samples 7 | recursive-include src *.c 8 | recursive-include src *.h 9 | recursive-include samples *.py *.sql 10 | recursive-include test *.py *.sql 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python cx_Oracle 2 | 3 | **cx_Oracle was obsoleted by 4 | [python-oracledb](https://oracle.github.io/python-oracledb/) in 2022.** 5 | 6 | Python-oracledb uses the same Python DB API as cx_Oracle, and has many new 7 | features. 8 | 9 | Install with: 10 | 11 | ``` 12 | python -m pip install oracledb 13 | ``` 14 | 15 | Usage is like: 16 | 17 | ``` 18 | import getpass 19 | import oracledb 20 | 21 | un = 'scott' 22 | cs = 'localhost/orclpdb1' 23 | pw = getpass.getpass(f'Enter password for {un}@{cs}: ') 24 | 25 | with oracledb.connect(user=un, password=pw, dsn=cs) as connection: 26 | with connection.cursor() as cursor: 27 | sql = 'select systimestamp from dual' 28 | for r in cursor.execute(sql): 29 | print(r) 30 | ``` 31 | 32 | The source code for python-oracledb is at 33 | [github.com/oracle/python-oracledb](https://github.com/oracle/python-oracledb). 34 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | cx_Oracle was obsoleted by python-oracledb in 2022. 2 | 3 | Python-oracledb uses the same Python DB API as cx_Oracle, and has many new 4 | features. 5 | 6 | See https://python-oracledb.readthedocs.io/en/latest/index.html for how to 7 | install and use this updated driver. 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security vulnerabilities 2 | 3 | Oracle values the independent security research community and believes that 4 | responsible disclosure of security vulnerabilities helps us ensure the security 5 | and privacy of all our users. 6 | 7 | Please do NOT raise a GitHub Issue to report a security vulnerability. If you 8 | believe you have found a security vulnerability, please submit a report to 9 | [secalert_us@oracle.com][1] preferably with a proof of concept. Please review 10 | some additional information on [how to report security vulnerabilities to 11 | Oracle][2]. We encourage people who contact Oracle Security to use email 12 | encryption using [our encryption key][3]. 13 | 14 | We ask that you do not use other channels or contact the project maintainers 15 | directly. 16 | 17 | Non-vulnerability related security issues including ideas for new or improved 18 | security features are welcome on GitHub Issues. 19 | 20 | ## Security updates, alerts and bulletins 21 | 22 | Security updates will be released on a regular cadence. Many of our projects 23 | will typically release security fixes in conjunction with the Oracle Critical 24 | Patch Update program. Additional information, including past advisories, is 25 | available on our [security alerts][4] page. 26 | 27 | ## Security-related information 28 | 29 | We will provide security related information such as a threat model, 30 | considerations for secure use, or any known security issues in our 31 | documentation. Please note that labs and sample code are intended to 32 | demonstrate a concept and may not be sufficiently hardened for production use. 33 | 34 | [1]: mailto:secalert_us@oracle.com 35 | [2]: https://www.oracle.com/corporate/security-practices/assurance/vulnerability/reporting.html 36 | [3]: https://www.oracle.com/security-alerts/encryptionkey.html 37 | [4]: https://www.oracle.com/security-alerts/ 38 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to generate cx_Oracle documentation using Sphinx 2 | 3 | SPHINXOPTS = 4 | SPHINXBUILD = sphinx-build 5 | SOURCEDIR = src 6 | BUILDDIR = build 7 | 8 | .PHONY: html 9 | html: 10 | @$(SPHINXBUILD) -M html $(SOURCEDIR) $(BUILDDIR) $(SPHINXOPTS) 11 | 12 | .PHONY: epub 13 | epub: 14 | @$(SPHINXBUILD) -M epub $(SOURCEDIR) $(BUILDDIR) $(SPHINXOPTS) 15 | 16 | .PHONY: pdf 17 | pdf: 18 | @$(SPHINXBUILD) -M latexpdf $(SOURCEDIR) $(BUILDDIR) $(SPHINXOPTS) 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -rf $(BUILDDIR)/* 23 | 24 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | The generated cx_Oracle documentation is at http://cx-oracle.readthedocs.io/ 2 | 3 | This directory contains the documentation source. It is written using reST 4 | (re-Structured Text) format source files which are processed using Sphinx and 5 | turned into HTML, PDF or ePub documents. If you wish to build these yourself, 6 | you need to install Sphinx. Sphinx is available on many Linux distributions as a 7 | pre-built package. You can also install Sphinx on all platforms using the Python 8 | package manager "pip". For more information on Sphinx, please visit this page: 9 | 10 | http://www.sphinx-doc.org 11 | 12 | Once Sphinx is installed, the supplied Makefile can be used to build the 13 | different targets. 14 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=4.2.0 2 | sphinx-rtd-theme>=0.5.2 3 | -------------------------------------------------------------------------------- /doc/src/api_manual/aq.rst: -------------------------------------------------------------------------------- 1 | .. _aq: 2 | 3 | ********************* 4 | Advanced Queuing (AQ) 5 | ********************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: Advanced Queueing (AQ) `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/connection.rst: -------------------------------------------------------------------------------- 1 | .. _connobj: 2 | 3 | ***************** 4 | Connection Object 5 | ***************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: Connection Objects `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/cursor.rst: -------------------------------------------------------------------------------- 1 | .. _cursorobj: 2 | 3 | ************* 4 | Cursor Object 5 | ************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: Cursor Objects `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/deprecations.rst: -------------------------------------------------------------------------------- 1 | .. _deprecations: 2 | 3 | ************ 4 | Deprecations 5 | ************ 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Deprecated and Desupported Features `__ in the python-oracledb 11 | documentation. 12 | -------------------------------------------------------------------------------- /doc/src/api_manual/lob.rst: -------------------------------------------------------------------------------- 1 | .. _lobobj: 2 | 3 | *********** 4 | LOB Objects 5 | *********** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: LOB Objects `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/module.rst: -------------------------------------------------------------------------------- 1 | .. module:: cx_Oracle 2 | 3 | .. _module: 4 | 5 | **************** 6 | Module Interface 7 | **************** 8 | 9 | .. include:: ../note.rst 10 | 11 | See `API: python-oracledb Module `__ in the python-oracledb documentation. 13 | -------------------------------------------------------------------------------- /doc/src/api_manual/object_type.rst: -------------------------------------------------------------------------------- 1 | .. _objecttype: 2 | 3 | ******************* 4 | Object Type Objects 5 | ******************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: DbObjectType Objects `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/session_pool.rst: -------------------------------------------------------------------------------- 1 | .. _sesspool: 2 | 3 | ****************** 4 | SessionPool Object 5 | ****************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: ConnectionPool Objects `__ in the python-oracledb 11 | documentation. 12 | -------------------------------------------------------------------------------- /doc/src/api_manual/soda.rst: -------------------------------------------------------------------------------- 1 | .. _soda: 2 | 3 | **** 4 | SODA 5 | **** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: SODA `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/subscription.rst: -------------------------------------------------------------------------------- 1 | .. _subscrobj: 2 | 3 | ******************* 4 | Subscription Object 5 | ******************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: Subscription Objects `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/api_manual/variable.rst: -------------------------------------------------------------------------------- 1 | .. _varobj: 2 | 3 | **************** 4 | Variable Objects 5 | **************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `API: Variable Objects `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # cx_Oracle documentation build configuration file 4 | # 5 | # This file is execfile()d with the current directory set to its containing dir. 6 | # 7 | # The contents of this file are pickled, so don't put values in the namespace 8 | # that aren't pickleable (module imports are okay, they're removed automatically). 9 | # 10 | # All configuration values have a default value; values that are commented out 11 | # serve to show the default value. 12 | 13 | import sys 14 | 15 | # If your extensions are in another directory, add it here. 16 | #sys.path.append('some/directory') 17 | 18 | # General configuration 19 | # --------------------- 20 | 21 | # Add any Sphinx extension module names here, as strings. They can be extensions 22 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 23 | extensions = ['sphinx_rtd_theme'] 24 | 25 | # Add any paths that contain templates here, relative to this directory. 26 | templates_path = ['.templates'] 27 | 28 | # The suffix of source filenames. 29 | source_suffix = '.rst' 30 | 31 | # The root toctree document. 32 | root_doc = master_doc = 'index' 33 | 34 | # General substitutions. 35 | project = 'cx_Oracle' 36 | copyright = u'2016, 2025, Oracle and/or its affiliates. All rights reserved. Portions Copyright © 2007-2015, Anthony Tuininga. All rights reserved. Portions Copyright © 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, Canada. All rights reserved' 37 | author = 'Oracle' 38 | 39 | # The default replacements for |version| and |release|, also used in various 40 | # other places throughout the built documents. 41 | # 42 | # The short X.Y version. 43 | version = '8.3' 44 | # The full version, including alpha/beta/rc tags. 45 | release = '8.3.0' 46 | 47 | # There are two options for replacing |today|: either, you set today to some 48 | # non-false value, then it is used: 49 | #today = '' 50 | # Else, today_fmt is used as the format for a strftime call. 51 | today_fmt = '%B %d, %Y' 52 | 53 | # List of documents that shouldn't be included in the build. 54 | #unused_docs = [] 55 | 56 | # If true, '()' will be appended to :func: etc. cross-reference text. 57 | #add_function_parentheses = True 58 | 59 | # If true, the current module name will be prepended to all description 60 | # unit titles (such as .. function::). 61 | #add_module_names = True 62 | 63 | # If true, sectionauthor and moduleauthor directives will be shown in the 64 | # output. They are ignored by default. 65 | #show_authors = False 66 | 67 | # The name of the Pygments (syntax highlighting) style to use. 68 | pygments_style = 'sphinx' 69 | 70 | 71 | # Options for HTML output 72 | # ----------------------- 73 | 74 | # The style sheet to use for HTML and HTML Help pages. A file of that name 75 | # must exist either in Sphinx' static/ path, or in one of the custom paths 76 | # given in html_static_path. 77 | # html_style = 'default.css' 78 | 79 | # The theme to use for ReadtheDocs. 80 | html_theme = "sphinx_rtd_theme" 81 | 82 | # Add any paths that contain custom static files (such as style sheets) here, 83 | # relative to this directory. They are copied after the builtin static files, 84 | # so a file named "default.css" will overwrite the builtin "default.css". 85 | #html_static_path = ['.static'] 86 | 87 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 88 | # using the given strftime format. 89 | html_last_updated_fmt = '%b %d, %Y' 90 | 91 | # If true, SmartyPants will be used to convert quotes and dashes to 92 | # typographically correct entities. 93 | #html_use_smartypants = True 94 | 95 | # Content template for the index page. 96 | #html_index = '' 97 | 98 | # Custom sidebar templates, maps document names to template names. 99 | #html_sidebars = {} 100 | 101 | # Additional templates that should be rendered to pages, maps page names to 102 | # template names. 103 | #html_additional_pages = {} 104 | 105 | # If false, no module index is generated. 106 | #html_use_modindex = True 107 | 108 | # If true, the reST sources are included in the HTML build as _sources/. 109 | html_copy_source = False 110 | 111 | # Output file base name for HTML help builder. 112 | htmlhelp_basename = 'cx_Oracledoc' 113 | 114 | numfig = True 115 | 116 | # Options for LaTeX output 117 | # ------------------------ 118 | 119 | # The paper size ('letter' or 'a4'). 120 | #latex_paper_size = 'letter' 121 | 122 | # The font size ('10pt', '11pt' or '12pt'). 123 | #latex_font_size = '10pt' 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, author, document class [howto/manual]). 127 | #latex_documents = [] 128 | 129 | # Additional stuff for the LaTeX preamble. 130 | #latex_preamble = '' 131 | 132 | # Documents to append as an appendix to all manuals. 133 | #latex_appendices = [] 134 | 135 | # If false, no module index is generated. 136 | #latex_use_modindex = True 137 | -------------------------------------------------------------------------------- /doc/src/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to cx_Oracle's documentation! 2 | ===================================== 3 | 4 | .. include:: note.rst 5 | 6 | See `python-oracledb documentation `__. 8 | 9 | Contents: 10 | 11 | User Guide 12 | ========== 13 | 14 | .. toctree:: 15 | :maxdepth: 3 16 | 17 | user_guide/introduction.rst 18 | user_guide/installation.rst 19 | user_guide/initialization.rst 20 | user_guide/connection_handling.rst 21 | user_guide/sql_execution.rst 22 | user_guide/plsql_execution.rst 23 | user_guide/bind.rst 24 | user_guide/lob_data.rst 25 | user_guide/json_data_type.rst 26 | user_guide/soda.rst 27 | user_guide/xml_data_type.rst 28 | user_guide/batch_statement.rst 29 | user_guide/exception_handling.rst 30 | user_guide/aq.rst 31 | user_guide/cqn.rst 32 | user_guide/txn_management.rst 33 | user_guide/tuning.rst 34 | user_guide/globalization.rst 35 | user_guide/startup.rst 36 | user_guide/ha.rst 37 | user_guide/tracing_sql.rst 38 | 39 | API Manual 40 | ========== 41 | 42 | .. toctree:: 43 | :maxdepth: 3 44 | 45 | api_manual/module.rst 46 | api_manual/connection.rst 47 | api_manual/cursor.rst 48 | api_manual/variable.rst 49 | api_manual/session_pool.rst 50 | api_manual/subscription.rst 51 | api_manual/lob.rst 52 | api_manual/object_type.rst 53 | api_manual/aq.rst 54 | api_manual/soda.rst 55 | api_manual/deprecations.rst 56 | 57 | 58 | Indices and tables 59 | ================== 60 | 61 | * :ref:`genindex` 62 | * :ref:`modindex` 63 | * :ref:`search` 64 | -------------------------------------------------------------------------------- /doc/src/license.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. _license: 4 | 5 | ******* 6 | License 7 | ******* 8 | 9 | .. include:: 10 | 11 | .. centered:: **LICENSE AGREEMENT FOR CX_ORACLE** 12 | 13 | Copyright |copy| 2016, 2025, Oracle and/or its affiliates. All rights reserved. 14 | 15 | Copyright |copy| 2007-2015, Anthony Tuininga. All rights reserved. 16 | 17 | Copyright |copy| 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 18 | Canada. All rights reserved. 19 | 20 | Redistribution and use in source and binary forms, with or without modification, 21 | are permitted provided that the following conditions are met: 22 | 23 | #. Redistributions of source code must retain the above copyright notice, this 24 | list of conditions, and the disclaimer that follows. 25 | 26 | #. Redistributions in binary form must reproduce the above copyright notice, 27 | this list of conditions, and the following disclaimer in the documentation 28 | and/or other materials provided with the distribution. 29 | 30 | #. Neither the names of the copyright holders nor the names of any contributors 31 | may be used to endorse or promote products derived from this software without 32 | specific prior written permission. 33 | 34 | DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 35 | \*AS IS\* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 36 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 37 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 38 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 39 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 40 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 41 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 42 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 43 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 | 45 | Computronix |reg| is a registered trademark of Computronix (Canada) Ltd. 46 | -------------------------------------------------------------------------------- /doc/src/note.rst: -------------------------------------------------------------------------------- 1 | .. note:: 2 | 3 | **cx_Oracle has a major new release under a new name and homepage** 4 | `python-oracledb `__. 5 | 6 | **New projects should install python-oracledb instead of the obsolete 7 | cx_Oracle driver.** 8 | 9 | Python-oracledb uses the same Python Database API as cx_Oracle, supports the 10 | feature requirements of frameworks that rely on this API, and has many new 11 | features. 12 | 13 | To upgrade to python-oracledb, see `Upgrading from cx_Oracle 8.3 to 14 | python-oracledb `__. 17 | -------------------------------------------------------------------------------- /doc/src/release_notes.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. _releasenotes: 4 | 5 | cx_Oracle Release Notes 6 | ======================= 7 | 8 | .. include:: note.rst 9 | 10 | See `python-oracledb Release Notes `__. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/aq.rst: -------------------------------------------------------------------------------- 1 | .. _aqusermanual: 2 | 3 | **************************** 4 | Oracle Advanced Queuing (AQ) 5 | **************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Using Oracle Transactional Event Queues and Advanced Queuing 10 | `__ in 11 | the python-oracledb documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/batch_statement.rst: -------------------------------------------------------------------------------- 1 | .. _batchstmnt: 2 | 3 | ****************************************** 4 | Batch Statement Execution and Bulk Loading 5 | ****************************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Executing Batch Statements and Bulk Loading `__ in the 11 | python-oracledb documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/bind.rst: -------------------------------------------------------------------------------- 1 | .. _bind: 2 | 3 | ******************** 4 | Using Bind Variables 5 | ******************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Using Bind Variables `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/connection_handling.rst: -------------------------------------------------------------------------------- 1 | .. _connhandling: 2 | 3 | ***************************** 4 | Connecting to Oracle Database 5 | ***************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Connecting to Oracle Database `__ in the python-oracledb 11 | documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/cqn.rst: -------------------------------------------------------------------------------- 1 | .. _cqn: 2 | 3 | *********************************** 4 | Continuous Query Notification (CQN) 5 | *********************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Working with Continuous Query Notification (CQN) `__ in the python-oracledb 11 | documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/exception_handling.rst: -------------------------------------------------------------------------------- 1 | .. _exception: 2 | 3 | ****************** 4 | Exception Handling 5 | ****************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Catching Exceptions `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/globalization.rst: -------------------------------------------------------------------------------- 1 | .. _globalization: 2 | 3 | ******************************** 4 | Character Sets and Globalization 5 | ******************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Character Sets and Globalization `__ in the python-oracledb 11 | documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/ha.rst: -------------------------------------------------------------------------------- 1 | .. _highavailability: 2 | 3 | ******************************** 4 | High Availability with cx_Oracle 5 | ******************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Using High Availability with python-oracledb `__ in the python-oracledb 11 | documentation. 12 | 13 | -------------------------------------------------------------------------------- /doc/src/user_guide/initialization.rst: -------------------------------------------------------------------------------- 1 | .. _initialization: 2 | 3 | ************************** 4 | cx_Oracle 8 Initialization 5 | ************************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Initializing python-oracledb `__ in the python-oracledb 11 | documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | ************************ 4 | cx_Oracle 8 Installation 5 | ************************ 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Installing python-oracledb `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/introduction.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | ************************* 4 | Introduction to cx_Oracle 5 | ************************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Introduction to the Python Driver for Oracle Database 10 | `__ in the python-oracledb documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/json_data_type.rst: -------------------------------------------------------------------------------- 1 | .. _jsondatatype: 2 | 3 | ******************************* 4 | Working with the JSON Data Type 5 | ******************************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Using JSON Data `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/lob_data.rst: -------------------------------------------------------------------------------- 1 | .. _lobdata: 2 | 3 | ************************ 4 | Using CLOB and BLOB Data 5 | ************************ 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Using CLOB, BLOB, NCLOB, and BFILE Data `__ in the 11 | python-oracledb documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/plsql_execution.rst: -------------------------------------------------------------------------------- 1 | .. _plsqlexecution: 2 | 3 | **************** 4 | PL/SQL Execution 5 | **************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Executing PL/SQL `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/soda.rst: -------------------------------------------------------------------------------- 1 | .. _sodausermanual: 2 | 3 | ************************************ 4 | Simple Oracle Document Access (SODA) 5 | ************************************ 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Working with Simple Oracle Document Access (SODA) `__ in the 11 | python-oracledb documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/sql_execution.rst: -------------------------------------------------------------------------------- 1 | .. _sqlexecution: 2 | 3 | ************* 4 | SQL Execution 5 | ************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Executing SQL `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/startup.rst: -------------------------------------------------------------------------------- 1 | .. _startup: 2 | 3 | ************************************* 4 | Starting and Stopping Oracle Database 5 | ************************************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Starting and Stopping Oracle Database `__ in the 11 | python-oracledb documentation. 12 | -------------------------------------------------------------------------------- /doc/src/user_guide/tracing_sql.rst: -------------------------------------------------------------------------------- 1 | .. _tracingsql: 2 | 3 | ********************************* 4 | Tracing SQL and PL/SQL Statements 5 | ********************************* 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Tracing python-oracledb `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/tuning.rst: -------------------------------------------------------------------------------- 1 | .. _tuning: 2 | 3 | **************** 4 | Tuning cx_Oracle 5 | **************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Tuning python-oracledb `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/txn_management.rst: -------------------------------------------------------------------------------- 1 | .. _txnmgmnt: 2 | 3 | ********************** 4 | Transaction Management 5 | ********************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Managing Transactions `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /doc/src/user_guide/xml_data_type.rst: -------------------------------------------------------------------------------- 1 | .. _xmldatatype: 2 | 3 | ******************** 4 | Working with XMLTYPE 5 | ******************** 6 | 7 | .. include:: ../note.rst 8 | 9 | See `Using XMLTYPE Data `__ in the python-oracledb documentation. 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 40.6.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # Samples 2 | 3 | **cx_Oracle has a major new release under a new name and homepage 4 | [python-oracledb](https://oracle.github.io/python-oracledb/). New projects 5 | should use python-oracledb instead of the obsolete cx_Oracle driver.** 6 | 7 | Python-oracledb uses the same Python Database API as cx_Oracle, supports the 8 | feature requirements of frameworks that rely on this API, and has many new 9 | features. 10 | 11 | **Python-oracledb samples can be found at 12 | [github.com/oracle/python-oracledb/tree/main/samples](https://github.com/oracle/python-oracledb/tree/main/samples).** 13 | 14 | To upgrade to python-oracledb, see [Upgrading from cx_Oracle 8.3 to 15 | python-oracledb](https://python-oracledb.readthedocs.io/en/latest/user_guide/appendix_c.html#upgrading-from-cx-oracle-8-3-to-python-oracledb). 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = cx_Oracle 3 | description = Python interface to Oracle 4 | long_description = file: README.md 5 | long_description_content_type = text/markdown 6 | keywords = Oracle, database 7 | author = "Anthony Tuininga", 8 | author_email = "anthony.tuininga@gmail.com", 9 | license = BSD License 10 | url = https://oracle.github.io/python-cx_Oracle 11 | project_urls = 12 | Installation = https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html 13 | Samples = https://github.com/oracle/python-cx_Oracle/tree/main/samples 14 | Documentation = http://cx-oracle.readthedocs.io 15 | Release Notes = https://cx-oracle.readthedocs.io/en/latest/release_notes.html#releasenotes 16 | Issues = https://github.com/oracle/python-cx_Oracle/issues 17 | Source = https://github.com/oracle/python-cx_Oracle 18 | python_requires = >=3.6 19 | classifiers = 20 | Development Status :: 6 - Mature 21 | Intended Audience :: Developers 22 | License :: OSI Approved :: BSD License 23 | Natural Language :: English 24 | Operating System :: OS Independent 25 | Programming Language :: C 26 | Programming Language :: Python :: 3 :: Only 27 | Topic :: Database 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup script for cx_Oracle. 3 | """ 4 | 5 | import os 6 | import pkg_resources 7 | import setuptools 8 | import sys 9 | 10 | # check minimum supported Python version 11 | if sys.version_info[:2] < (3, 6): 12 | raise Exception("Python 3.6 or higher is required. " + 13 | "For python 2, use 'pip install cx_Oracle==7.3'") 14 | 15 | # check minimum supported version of setuptools 16 | pkg_resources.require("setuptools>=40.6.0") 17 | 18 | # define build constants 19 | BUILD_VERSION = "8.3.0" 20 | 21 | # setup extra link and compile args 22 | extra_link_args = [] 23 | extra_compile_args = [] 24 | if sys.platform == "aix4": 25 | extra_compile_args.append("-qcpluscmt") 26 | elif sys.platform == "aix5": 27 | extra_compile_args.append("-DAIX5") 28 | elif sys.platform == "cygwin": 29 | extra_link_args.append("-Wl,--enable-runtime-pseudo-reloc") 30 | elif sys.platform == "darwin": 31 | extra_link_args.append("-shared-libgcc") 32 | 33 | # define cx_Oracle sources 34 | source_dir = "src" 35 | sources = [os.path.join(source_dir, n) \ 36 | for n in sorted(os.listdir(source_dir)) if n.endswith(".c")] 37 | depends = ["src/cxoModule.h"] 38 | 39 | # define ODPI-C sources, libraries and include directories; if the environment 40 | # variables ODPIC_INC_DIR and ODPIC_LIB_DIR are both set, assume these 41 | # locations contain a compiled installation of ODPI-C; otherwise, use the 42 | # source of ODPI-C found in the odpi subdirectory 43 | dpi_include_dir = os.environ.get("ODPIC_INC_DIR") 44 | dpi_lib_dir = os.environ.get("ODPIC_LIB_DIR") 45 | if dpi_include_dir and dpi_lib_dir: 46 | dpi_sources = [] 47 | include_dirs = [dpi_include_dir] 48 | libraries = ["odpic"] 49 | library_dirs = [dpi_lib_dir] 50 | else: 51 | include_dirs = ["odpi/include", "odpi/src"] 52 | dpi_source_dir = os.path.join("odpi", "src") 53 | dpi_sources = [os.path.join(dpi_source_dir, n) \ 54 | for n in sorted(os.listdir(dpi_source_dir)) if n.endswith(".c")] 55 | depends.extend(["odpi/include/dpi.h", "odpi/src/dpiImpl.h", 56 | "odpi/src/dpiErrorMessages.h"]) 57 | libraries = [] 58 | library_dirs = [] 59 | 60 | # setup the extension 61 | extension = setuptools.Extension( 62 | name="cx_Oracle", 63 | include_dirs=include_dirs, 64 | extra_compile_args=extra_compile_args, 65 | define_macros=[("CXO_BUILD_VERSION", BUILD_VERSION)], 66 | extra_link_args=extra_link_args, 67 | sources=sources + dpi_sources, 68 | depends=depends, 69 | libraries=libraries, 70 | library_dirs=library_dirs) 71 | 72 | # perform the setup 73 | setuptools.setup( 74 | version=BUILD_VERSION, 75 | data_files=[ ("cx_Oracle-doc", ["LICENSE.txt", "README.txt"]) ], 76 | ext_modules=[extension]) 77 | -------------------------------------------------------------------------------- /src/cxoApiType.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoApiType.c 7 | // Defines the objects used for identifying types defined by the Python 8 | // Database API. 9 | //----------------------------------------------------------------------------- 10 | 11 | #include "cxoModule.h" 12 | 13 | //----------------------------------------------------------------------------- 14 | // cxoApiType_free() 15 | // Free the API type object. 16 | //----------------------------------------------------------------------------- 17 | static void cxoApiType_free(cxoApiType *apiType) 18 | { 19 | Py_CLEAR(apiType->dbTypes); 20 | Py_TYPE(apiType)->tp_free((PyObject*) apiType); 21 | } 22 | 23 | 24 | //----------------------------------------------------------------------------- 25 | // cxoApiType_repr() 26 | // Return a string representation of a queue. 27 | //----------------------------------------------------------------------------- 28 | static PyObject *cxoApiType_repr(cxoApiType *apiType) 29 | { 30 | PyObject *module, *name, *apiTypeName, *result; 31 | 32 | apiTypeName = PyUnicode_DecodeASCII(apiType->name, strlen(apiType->name), 33 | NULL); 34 | if (!apiTypeName) 35 | return NULL; 36 | if (cxoUtils_getModuleAndName(Py_TYPE(apiType), &module, &name) < 0) { 37 | Py_DECREF(apiTypeName); 38 | return NULL; 39 | } 40 | result = cxoUtils_formatString("<%s.%s %s>", 41 | PyTuple_Pack(3, module, name, apiTypeName)); 42 | Py_DECREF(module); 43 | Py_DECREF(name); 44 | Py_DECREF(apiTypeName); 45 | return result; 46 | } 47 | 48 | 49 | //----------------------------------------------------------------------------- 50 | // cxoApiType_reduce() 51 | // Method provided for pickling/unpickling of API types. 52 | //----------------------------------------------------------------------------- 53 | static PyObject *cxoApiType_reduce(cxoApiType *apiType) 54 | { 55 | return PyUnicode_DecodeASCII(apiType->name, strlen(apiType->name), NULL); 56 | } 57 | 58 | 59 | //----------------------------------------------------------------------------- 60 | // declaration of methods 61 | //----------------------------------------------------------------------------- 62 | static PyMethodDef cxoMethods[] = { 63 | { "__reduce__", (PyCFunction) cxoApiType_reduce, METH_NOARGS }, 64 | { NULL, NULL } 65 | }; 66 | 67 | 68 | //----------------------------------------------------------------------------- 69 | // declaration of members 70 | //----------------------------------------------------------------------------- 71 | static PyMemberDef cxoMembers[] = { 72 | { "name", T_STRING, offsetof(cxoApiType, name), READONLY }, 73 | { NULL } 74 | }; 75 | 76 | 77 | //----------------------------------------------------------------------------- 78 | // Python type declaration 79 | //----------------------------------------------------------------------------- 80 | PyTypeObject cxoPyTypeApiType = { 81 | PyVarObject_HEAD_INIT(NULL, 0) 82 | .tp_name = "cx_Oracle.ApiType", 83 | .tp_basicsize = sizeof(cxoApiType), 84 | .tp_dealloc = (destructor) cxoApiType_free, 85 | .tp_repr = (reprfunc) cxoApiType_repr, 86 | .tp_flags = Py_TPFLAGS_DEFAULT, 87 | .tp_members = cxoMembers, 88 | .tp_methods = cxoMethods 89 | }; 90 | -------------------------------------------------------------------------------- /src/cxoBuffer.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | // 4 | // Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | // 6 | // Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | // Canada. All rights reserved. 8 | //----------------------------------------------------------------------------- 9 | 10 | //----------------------------------------------------------------------------- 11 | // cxoBuffer.c 12 | // Defines buffer structure and routines for populating it. These are used 13 | // to translate Python objects into the buffers needed for Oracle, including 14 | // Unicode or buffer objects. 15 | //----------------------------------------------------------------------------- 16 | 17 | #include "cxoModule.h" 18 | 19 | //----------------------------------------------------------------------------- 20 | // cxoBuffer_fromObject() 21 | // Populate the string buffer from a unicode object. 22 | //----------------------------------------------------------------------------- 23 | int cxoBuffer_fromObject(cxoBuffer *buf, PyObject *obj, const char *encoding) 24 | { 25 | cxoBuffer_init(buf); 26 | if (!obj || obj == Py_None) 27 | return 0; 28 | if (PyUnicode_Check(obj)) { 29 | buf->obj = PyUnicode_AsEncodedString(obj, encoding, NULL); 30 | if (!buf->obj) 31 | return -1; 32 | buf->ptr = PyBytes_AS_STRING(buf->obj); 33 | buf->size = (uint32_t) PyBytes_GET_SIZE(buf->obj); 34 | buf->numCharacters = (uint32_t) PyUnicode_GET_LENGTH(obj); 35 | } else if (PyBytes_Check(obj)) { 36 | Py_INCREF(obj); 37 | buf->obj = obj; 38 | buf->ptr = PyBytes_AS_STRING(buf->obj); 39 | buf->size = buf->numCharacters = (uint32_t) PyBytes_GET_SIZE(buf->obj); 40 | } else { 41 | PyErr_SetString(PyExc_TypeError, "expecting string or bytes object"); 42 | return -1; 43 | } 44 | return 0; 45 | } 46 | 47 | 48 | //----------------------------------------------------------------------------- 49 | // cxoBuffer_init() 50 | // Initialize the buffer with an empty string. Returns 0 as a convenience to 51 | // the caller. 52 | //----------------------------------------------------------------------------- 53 | int cxoBuffer_init(cxoBuffer *buf) 54 | { 55 | buf->ptr = NULL; 56 | buf->size = 0; 57 | buf->numCharacters = 0; 58 | buf->obj = NULL; 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/cxoDbType.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoDbType.c 7 | // Defines the objects used for identifying all types used by the database. 8 | //----------------------------------------------------------------------------- 9 | 10 | #include "cxoModule.h" 11 | 12 | //----------------------------------------------------------------------------- 13 | // cxoDbType_free() 14 | // Free the database type object. 15 | //----------------------------------------------------------------------------- 16 | static void cxoDbType_free(cxoDbType *dbType) 17 | { 18 | Py_TYPE(dbType)->tp_free((PyObject*) dbType); 19 | } 20 | 21 | 22 | //----------------------------------------------------------------------------- 23 | // cxoDbType_repr() 24 | // Return a string representation of a queue. 25 | //----------------------------------------------------------------------------- 26 | static PyObject *cxoDbType_repr(cxoDbType *dbType) 27 | { 28 | PyObject *module, *name, *dbTypeName, *result; 29 | 30 | dbTypeName = PyUnicode_DecodeASCII(dbType->name, strlen(dbType->name), 31 | NULL); 32 | if (!dbTypeName) 33 | return NULL; 34 | if (cxoUtils_getModuleAndName(Py_TYPE(dbType), &module, &name) < 0) { 35 | Py_DECREF(dbTypeName); 36 | return NULL; 37 | } 38 | result = cxoUtils_formatString("<%s.%s %s>", 39 | PyTuple_Pack(3, module, name, dbTypeName)); 40 | Py_DECREF(module); 41 | Py_DECREF(name); 42 | Py_DECREF(dbTypeName); 43 | return result; 44 | } 45 | 46 | 47 | //----------------------------------------------------------------------------- 48 | // cxoDbType_richCompare() 49 | // Peforms a comparison between the database type and another Python object. 50 | // Equality (and inequality) are used to match database API types with their 51 | // associated database types. 52 | //----------------------------------------------------------------------------- 53 | static PyObject *cxoDbType_richCompare(cxoDbType* dbType, PyObject* obj, 54 | int op) 55 | { 56 | cxoApiType *apiType; 57 | int status, equal; 58 | 59 | // only equality and inequality can be checked 60 | if (op != Py_EQ && op != Py_NE) { 61 | Py_INCREF(Py_NotImplemented); 62 | return Py_NotImplemented; 63 | } 64 | 65 | // check for exact object 66 | equal = 0; 67 | if (obj == (PyObject*) dbType) { 68 | equal = 1; 69 | 70 | // check for API type 71 | } else { 72 | status = PyObject_IsInstance(obj, (PyObject*) &cxoPyTypeApiType); 73 | if (status < 0) 74 | return NULL; 75 | if (status == 1) { 76 | apiType = (cxoApiType*) obj; 77 | status = PySequence_Contains(apiType->dbTypes, (PyObject*) dbType); 78 | if (status < 0) 79 | return NULL; 80 | equal = (status == 1) ? 1 : 0; 81 | } 82 | } 83 | 84 | // determine return value 85 | if ((equal && op == Py_EQ) || (!equal && op == Py_NE)) { 86 | Py_RETURN_TRUE; 87 | } 88 | Py_RETURN_FALSE; 89 | } 90 | 91 | 92 | //----------------------------------------------------------------------------- 93 | // cxoDbType_hash() 94 | // Return a hash value for the instance. 95 | //----------------------------------------------------------------------------- 96 | static Py_hash_t cxoDbType_hash(cxoDbType *dbType) 97 | { 98 | return (Py_hash_t) dbType->num; 99 | } 100 | 101 | 102 | //----------------------------------------------------------------------------- 103 | // cxoDbType_fromDataTypeInfo() 104 | // Return the database type given the data type info available from ODPI-C. 105 | //----------------------------------------------------------------------------- 106 | cxoDbType *cxoDbType_fromDataTypeInfo(dpiDataTypeInfo *info) 107 | { 108 | char message[120]; 109 | 110 | switch (info->oracleTypeNum) { 111 | case DPI_ORACLE_TYPE_VARCHAR: 112 | return cxoDbTypeVarchar; 113 | case DPI_ORACLE_TYPE_NVARCHAR: 114 | return cxoDbTypeNvarchar; 115 | case DPI_ORACLE_TYPE_CHAR: 116 | return cxoDbTypeChar; 117 | case DPI_ORACLE_TYPE_NCHAR: 118 | return cxoDbTypeNchar; 119 | case DPI_ORACLE_TYPE_ROWID: 120 | return cxoDbTypeRowid; 121 | case DPI_ORACLE_TYPE_RAW: 122 | return cxoDbTypeRaw; 123 | case DPI_ORACLE_TYPE_NATIVE_DOUBLE: 124 | return cxoDbTypeBinaryDouble; 125 | case DPI_ORACLE_TYPE_NATIVE_FLOAT: 126 | return cxoDbTypeBinaryFloat; 127 | case DPI_ORACLE_TYPE_NATIVE_INT: 128 | return cxoDbTypeBinaryInteger; 129 | case DPI_ORACLE_TYPE_NUMBER: 130 | return cxoDbTypeNumber; 131 | case DPI_ORACLE_TYPE_DATE: 132 | return cxoDbTypeDate; 133 | case DPI_ORACLE_TYPE_TIMESTAMP: 134 | return cxoDbTypeTimestamp; 135 | case DPI_ORACLE_TYPE_TIMESTAMP_TZ: 136 | return cxoDbTypeTimestampTZ; 137 | case DPI_ORACLE_TYPE_TIMESTAMP_LTZ: 138 | return cxoDbTypeTimestampLTZ; 139 | case DPI_ORACLE_TYPE_INTERVAL_DS: 140 | return cxoDbTypeIntervalDS; 141 | case DPI_ORACLE_TYPE_INTERVAL_YM: 142 | return cxoDbTypeIntervalYM; 143 | case DPI_ORACLE_TYPE_CLOB: 144 | return cxoDbTypeClob; 145 | case DPI_ORACLE_TYPE_NCLOB: 146 | return cxoDbTypeNclob; 147 | case DPI_ORACLE_TYPE_BLOB: 148 | return cxoDbTypeBlob; 149 | case DPI_ORACLE_TYPE_BFILE: 150 | return cxoDbTypeBfile; 151 | case DPI_ORACLE_TYPE_STMT: 152 | return cxoDbTypeCursor; 153 | case DPI_ORACLE_TYPE_OBJECT: 154 | return cxoDbTypeObject; 155 | case DPI_ORACLE_TYPE_LONG_VARCHAR: 156 | return cxoDbTypeLong; 157 | case DPI_ORACLE_TYPE_LONG_RAW: 158 | return cxoDbTypeLongRaw; 159 | case DPI_ORACLE_TYPE_BOOLEAN: 160 | return cxoDbTypeBoolean; 161 | default: 162 | break; 163 | } 164 | 165 | snprintf(message, sizeof(message), "Oracle type %d not supported.", 166 | info->oracleTypeNum); 167 | cxoError_raiseFromString(cxoNotSupportedErrorException, message); 168 | return NULL; 169 | } 170 | 171 | 172 | //----------------------------------------------------------------------------- 173 | // cxoDbType_fromTransformNum() 174 | // Return the database type given the transformation number. 175 | //----------------------------------------------------------------------------- 176 | cxoDbType *cxoDbType_fromTransformNum(cxoTransformNum transformNum) 177 | { 178 | char message[120]; 179 | 180 | switch (transformNum) { 181 | case CXO_TRANSFORM_BINARY: 182 | return cxoDbTypeRaw; 183 | case CXO_TRANSFORM_BFILE: 184 | return cxoDbTypeBfile; 185 | case CXO_TRANSFORM_BLOB: 186 | return cxoDbTypeBlob; 187 | case CXO_TRANSFORM_BOOLEAN: 188 | return cxoDbTypeBoolean; 189 | case CXO_TRANSFORM_CLOB: 190 | return cxoDbTypeClob; 191 | case CXO_TRANSFORM_CURSOR: 192 | return cxoDbTypeCursor; 193 | case CXO_TRANSFORM_DATE: 194 | case CXO_TRANSFORM_DATETIME: 195 | return cxoDbTypeDate; 196 | case CXO_TRANSFORM_DECIMAL: 197 | case CXO_TRANSFORM_FLOAT: 198 | case CXO_TRANSFORM_INT: 199 | return cxoDbTypeNumber; 200 | case CXO_TRANSFORM_FIXED_CHAR: 201 | return cxoDbTypeChar; 202 | case CXO_TRANSFORM_FIXED_NCHAR: 203 | return cxoDbTypeNchar; 204 | case CXO_TRANSFORM_LONG_BINARY: 205 | return cxoDbTypeLongRaw; 206 | case CXO_TRANSFORM_LONG_STRING: 207 | return cxoDbTypeLong; 208 | case CXO_TRANSFORM_NATIVE_DOUBLE: 209 | return cxoDbTypeBinaryDouble; 210 | case CXO_TRANSFORM_NATIVE_FLOAT: 211 | return cxoDbTypeBinaryFloat; 212 | case CXO_TRANSFORM_NATIVE_INT: 213 | return cxoDbTypeBinaryInteger; 214 | case CXO_TRANSFORM_NCLOB: 215 | return cxoDbTypeNclob; 216 | case CXO_TRANSFORM_NSTRING: 217 | return cxoDbTypeNvarchar; 218 | case CXO_TRANSFORM_OBJECT: 219 | return cxoDbTypeObject; 220 | case CXO_TRANSFORM_ROWID: 221 | return cxoDbTypeRowid; 222 | case CXO_TRANSFORM_NONE: 223 | case CXO_TRANSFORM_STRING: 224 | return cxoDbTypeVarchar; 225 | case CXO_TRANSFORM_TIMEDELTA: 226 | return cxoDbTypeIntervalDS; 227 | case CXO_TRANSFORM_TIMESTAMP: 228 | return cxoDbTypeTimestamp; 229 | case CXO_TRANSFORM_TIMESTAMP_LTZ: 230 | return cxoDbTypeTimestampLTZ; 231 | case CXO_TRANSFORM_TIMESTAMP_TZ: 232 | return cxoDbTypeTimestampTZ; 233 | case CXO_TRANSFORM_JSON: 234 | return cxoDbTypeJson; 235 | default: 236 | break; 237 | } 238 | 239 | snprintf(message, sizeof(message), "transform %d not supported.", 240 | transformNum); 241 | cxoError_raiseFromString(cxoNotSupportedErrorException, message); 242 | return NULL; 243 | } 244 | 245 | 246 | //----------------------------------------------------------------------------- 247 | // cxoDBType_reduce() 248 | // Method provided for pickling/unpickling of DB types. 249 | //----------------------------------------------------------------------------- 250 | static PyObject *cxoDBType_reduce(cxoDbType *dbType) 251 | { 252 | return PyUnicode_DecodeASCII(dbType->name, strlen(dbType->name), NULL); 253 | } 254 | 255 | 256 | //----------------------------------------------------------------------------- 257 | // declaration of methods 258 | //----------------------------------------------------------------------------- 259 | static PyMethodDef cxoMethods[] = { 260 | { "__reduce__", (PyCFunction) cxoDBType_reduce, METH_NOARGS }, 261 | { NULL, NULL} 262 | }; 263 | 264 | 265 | //----------------------------------------------------------------------------- 266 | // declaration of members 267 | //----------------------------------------------------------------------------- 268 | static PyMemberDef cxoMembers[] = { 269 | { "name", T_STRING, offsetof(cxoDbType, name), READONLY }, 270 | { NULL } 271 | }; 272 | 273 | 274 | //----------------------------------------------------------------------------- 275 | // Python type declaration 276 | //----------------------------------------------------------------------------- 277 | PyTypeObject cxoPyTypeDbType = { 278 | PyVarObject_HEAD_INIT(NULL, 0) 279 | .tp_name = "cx_Oracle.DbType", 280 | .tp_basicsize = sizeof(cxoDbType), 281 | .tp_dealloc = (destructor) cxoDbType_free, 282 | .tp_repr = (reprfunc) cxoDbType_repr, 283 | .tp_flags = Py_TPFLAGS_DEFAULT, 284 | .tp_members = cxoMembers, 285 | .tp_methods = cxoMethods, 286 | .tp_richcompare = (richcmpfunc) cxoDbType_richCompare, 287 | .tp_hash = (hashfunc) cxoDbType_hash 288 | }; 289 | -------------------------------------------------------------------------------- /src/cxoEnqOptions.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | // 4 | // Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | // 6 | // Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | // Canada. All rights reserved. 8 | //----------------------------------------------------------------------------- 9 | 10 | //----------------------------------------------------------------------------- 11 | // cxoEnqOptions.c 12 | // Implements the enqueue options objects used in Advanced Queuing. 13 | //----------------------------------------------------------------------------- 14 | 15 | #include "cxoModule.h" 16 | 17 | //----------------------------------------------------------------------------- 18 | // cxoEnqOptions_new() 19 | // Create a new enqueue options object. 20 | //----------------------------------------------------------------------------- 21 | cxoEnqOptions *cxoEnqOptions_new(cxoConnection *connection, 22 | dpiEnqOptions *handle) 23 | { 24 | cxoEnqOptions *options; 25 | int status; 26 | 27 | options = (cxoEnqOptions*) 28 | cxoPyTypeEnqOptions.tp_alloc(&cxoPyTypeEnqOptions, 0); 29 | if (!options) 30 | return NULL; 31 | if (handle) { 32 | status = dpiEnqOptions_addRef(handle); 33 | } else { 34 | status = dpiConn_newEnqOptions(connection->handle, &handle); 35 | } 36 | if (status < 0) { 37 | cxoError_raiseAndReturnNull(); 38 | Py_DECREF(options); 39 | return NULL; 40 | } 41 | options->handle = handle; 42 | options->encoding = connection->encodingInfo.encoding; 43 | 44 | return options; 45 | } 46 | 47 | 48 | //----------------------------------------------------------------------------- 49 | // cxoEnqOptions_free() 50 | // Free the memory associated with the enqueue options object. 51 | //----------------------------------------------------------------------------- 52 | static void cxoEnqOptions_free(cxoEnqOptions *self) 53 | { 54 | if (self->handle) { 55 | dpiEnqOptions_release(self->handle); 56 | self->handle = NULL; 57 | } 58 | Py_TYPE(self)->tp_free((PyObject*) self); 59 | } 60 | 61 | 62 | //----------------------------------------------------------------------------- 63 | // cxoEnqOptions_getTransformation() 64 | // Get the value of the transformation option. 65 | //----------------------------------------------------------------------------- 66 | static PyObject *cxoEnqOptions_getTransformation(cxoEnqOptions *self, 67 | void *unused) 68 | { 69 | uint32_t valueLength; 70 | const char *value; 71 | 72 | if (dpiEnqOptions_getTransformation(self->handle, &value, 73 | &valueLength) < 0) 74 | return cxoError_raiseAndReturnNull(); 75 | if (!value) 76 | Py_RETURN_NONE; 77 | return PyUnicode_Decode(value, valueLength, self->encoding, NULL); 78 | } 79 | 80 | 81 | //----------------------------------------------------------------------------- 82 | // cxoEnqOptions_getVisibility() 83 | // Get the value of the visibility option. 84 | //----------------------------------------------------------------------------- 85 | static PyObject *cxoEnqOptions_getVisibility(cxoEnqOptions *self, void *unused) 86 | { 87 | dpiVisibility value; 88 | 89 | if (dpiEnqOptions_getVisibility(self->handle, &value) < 0) 90 | return cxoError_raiseAndReturnNull(); 91 | return PyLong_FromLong(value); 92 | } 93 | 94 | 95 | //----------------------------------------------------------------------------- 96 | // cxoEnqOptions_setDeliveryMode() 97 | // Set the value of the delivery mode option. 98 | //----------------------------------------------------------------------------- 99 | static int cxoEnqOptions_setDeliveryMode(cxoEnqOptions *self, PyObject *valueObj, 100 | void *unused) 101 | { 102 | dpiMessageDeliveryMode value; 103 | 104 | value = PyLong_AsLong(valueObj); 105 | if (PyErr_Occurred()) 106 | return -1; 107 | if (dpiEnqOptions_setDeliveryMode(self->handle, value) < 0) 108 | return cxoError_raiseAndReturnInt(); 109 | return 0; 110 | } 111 | 112 | 113 | //----------------------------------------------------------------------------- 114 | // cxoEnqOptions_setTransformation() 115 | // Set the value of the transformation option. 116 | //----------------------------------------------------------------------------- 117 | static int cxoEnqOptions_setTransformation(cxoEnqOptions *self, 118 | PyObject *valueObj, void *unused) 119 | { 120 | cxoBuffer buffer; 121 | int status; 122 | 123 | if (cxoBuffer_fromObject(&buffer, valueObj, self->encoding) < 0) 124 | return -1; 125 | status = dpiEnqOptions_setTransformation(self->handle, buffer.ptr, 126 | buffer.size); 127 | cxoBuffer_clear(&buffer); 128 | if (status < 0) 129 | return cxoError_raiseAndReturnInt(); 130 | return 0; 131 | } 132 | 133 | 134 | //----------------------------------------------------------------------------- 135 | // cxoEnqOptions_setVisibility() 136 | // Set the value of the visibility option. 137 | //----------------------------------------------------------------------------- 138 | static int cxoEnqOptions_setVisibility(cxoEnqOptions *self, 139 | PyObject *valueObj, void *unused) 140 | { 141 | dpiVisibility value; 142 | 143 | value = PyLong_AsLong(valueObj); 144 | if (PyErr_Occurred()) 145 | return -1; 146 | if (dpiEnqOptions_setVisibility(self->handle, value) < 0) 147 | return cxoError_raiseAndReturnInt(); 148 | return 0; 149 | } 150 | 151 | 152 | //----------------------------------------------------------------------------- 153 | // declaration of calculated members for Python type 154 | //----------------------------------------------------------------------------- 155 | static PyGetSetDef cxoEnqOptionsCalcMembers[] = { 156 | { "deliverymode", 0, (setter) cxoEnqOptions_setDeliveryMode, 0, 0 }, 157 | { "transformation", (getter) cxoEnqOptions_getTransformation, 158 | (setter) cxoEnqOptions_setTransformation, 0, 0 }, 159 | { "visibility", (getter) cxoEnqOptions_getVisibility, 160 | (setter) cxoEnqOptions_setVisibility, 0, 0 }, 161 | { NULL } 162 | }; 163 | 164 | 165 | //----------------------------------------------------------------------------- 166 | // declaration of Python type 167 | //----------------------------------------------------------------------------- 168 | PyTypeObject cxoPyTypeEnqOptions = { 169 | PyVarObject_HEAD_INIT(NULL, 0) 170 | .tp_name = "cx_Oracle.EnqOptions", 171 | .tp_basicsize = sizeof(cxoEnqOptions), 172 | .tp_dealloc = (destructor) cxoEnqOptions_free, 173 | .tp_flags = Py_TPFLAGS_DEFAULT, 174 | .tp_getset = cxoEnqOptionsCalcMembers 175 | }; 176 | -------------------------------------------------------------------------------- /src/cxoError.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | // 4 | // Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | // 6 | // Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | // Canada. All rights reserved. 8 | //----------------------------------------------------------------------------- 9 | 10 | //----------------------------------------------------------------------------- 11 | // cxoError.c 12 | // Error handling. 13 | //----------------------------------------------------------------------------- 14 | 15 | #include "cxoModule.h" 16 | 17 | //----------------------------------------------------------------------------- 18 | // cxoError_free() 19 | // Deallocate the error. 20 | //----------------------------------------------------------------------------- 21 | static void cxoError_free(cxoError *error) 22 | { 23 | Py_CLEAR(error->message); 24 | Py_CLEAR(error->context); 25 | PyObject_Del(error); 26 | } 27 | 28 | 29 | //----------------------------------------------------------------------------- 30 | // cxoError_new() 31 | // Create a new error object. This is intended to only be used by the 32 | // unpickling routine, and not by direct creation! 33 | //----------------------------------------------------------------------------- 34 | static PyObject *cxoError_new(PyTypeObject *type, PyObject *args, 35 | PyObject *keywordArgs) 36 | { 37 | PyObject *message, *context; 38 | int isRecoverable, code; 39 | cxoError *error; 40 | unsigned offset; 41 | 42 | isRecoverable = 0; 43 | if (!PyArg_ParseTuple(args, "OiIO|i", &message, &code, &offset, &context, 44 | &isRecoverable)) 45 | return NULL; 46 | error = (cxoError*) type->tp_alloc(type, 0); 47 | if (!error) 48 | return NULL; 49 | 50 | error->code = code; 51 | error->offset = offset; 52 | error->isRecoverable = (char) isRecoverable; 53 | Py_INCREF(message); 54 | error->message = message; 55 | Py_INCREF(context); 56 | error->context = context; 57 | 58 | return (PyObject*) error; 59 | } 60 | 61 | 62 | //----------------------------------------------------------------------------- 63 | // cxoError_newFromInfo() 64 | // Internal method for creating an error object from the DPI error 65 | // information. 66 | //----------------------------------------------------------------------------- 67 | cxoError *cxoError_newFromInfo(dpiErrorInfo *errorInfo) 68 | { 69 | cxoError *error; 70 | 71 | // create error object and initialize it 72 | error = (cxoError*) cxoPyTypeError.tp_alloc(&cxoPyTypeError, 0); 73 | if (!error) 74 | return NULL; 75 | error->code = errorInfo->code; 76 | error->offset = errorInfo->offset; 77 | error->isRecoverable = (char) errorInfo->isRecoverable; 78 | 79 | // create message 80 | error->message = PyUnicode_Decode(errorInfo->message, 81 | errorInfo->messageLength, errorInfo->encoding, NULL); 82 | if (!error->message) { 83 | Py_DECREF(error); 84 | return NULL; 85 | } 86 | 87 | // create context composed of function name and action 88 | error->context = PyUnicode_FromFormat("%s: %s", errorInfo->fnName, 89 | errorInfo->action); 90 | if (!error->context) { 91 | Py_DECREF(error); 92 | return NULL; 93 | } 94 | 95 | return error; 96 | } 97 | 98 | 99 | //----------------------------------------------------------------------------- 100 | // cxoError_newFromString() 101 | // Internal method for creating an error object from the DPI error 102 | // information. 103 | //----------------------------------------------------------------------------- 104 | static cxoError *cxoError_newFromString(const char *message) 105 | { 106 | cxoError *error; 107 | 108 | error = (cxoError*) cxoPyTypeError.tp_alloc(&cxoPyTypeError, 0); 109 | if (!error) 110 | return NULL; 111 | Py_INCREF(Py_None); 112 | error->context = Py_None; 113 | error->message = PyUnicode_DecodeASCII(message, strlen(message), NULL); 114 | if (!error->message) { 115 | Py_DECREF(error); 116 | return NULL; 117 | } 118 | 119 | return error; 120 | } 121 | 122 | 123 | //----------------------------------------------------------------------------- 124 | // cxoError_raiseAndReturnInt() 125 | // Internal method for raising an exception from an error generated from DPI. 126 | // Return -1 as a convenience to the caller. 127 | //----------------------------------------------------------------------------- 128 | int cxoError_raiseAndReturnInt(void) 129 | { 130 | dpiErrorInfo errorInfo; 131 | 132 | dpiContext_getError(cxoDpiContext, &errorInfo); 133 | return cxoError_raiseFromInfo(&errorInfo); 134 | } 135 | 136 | 137 | //----------------------------------------------------------------------------- 138 | // cxoError_raiseAndReturnNull() 139 | // Internal method for raising an exception from an error generated from DPI. 140 | // Return NULL as a convenience to the caller. 141 | //----------------------------------------------------------------------------- 142 | PyObject *cxoError_raiseAndReturnNull(void) 143 | { 144 | cxoError_raiseAndReturnInt(); 145 | return NULL; 146 | } 147 | 148 | 149 | //----------------------------------------------------------------------------- 150 | // cxoError_raiseFromInfo() 151 | // Internal method for raising an exception given an error information 152 | // structure from DPI. Return -1 as a convenience to the caller. 153 | //----------------------------------------------------------------------------- 154 | int cxoError_raiseFromInfo(dpiErrorInfo *errorInfo) 155 | { 156 | PyObject *exceptionType; 157 | cxoError *error; 158 | 159 | error = cxoError_newFromInfo(errorInfo); 160 | if (!error) 161 | return -1; 162 | switch (errorInfo->code) { 163 | case 1: // unique constraint violated 164 | case 1400: // cannot insert NULL 165 | case 2290: // check constraint violated 166 | case 2291: // integrity constraint violated - parent key not found 167 | case 2292: // integrity constraint violated - child record found 168 | case 40479: // internal JSON serializer error 169 | exceptionType = cxoIntegrityErrorException; 170 | break; 171 | case 22: // invalid session ID; access denied 172 | case 378: // buffer pools cannot be created as specified 173 | case 600: // internal error code 174 | case 602: // internal programming exception 175 | case 603: // ORACLE server session terminated by fatal error 176 | case 604: // error occurred at recursive SQL level 177 | case 609: // could not attach to incoming connection 178 | case 1012: // not logged on 179 | case 1013: // user requested cancel of current operation 180 | case 1033: // ORACLE initialization or shutdown in progress 181 | case 1034: // ORACLE not available 182 | case 1041: // internal error. hostdef extension doesn't exist 183 | case 1043: // user side memory corruption 184 | case 1089: // immediate shutdown or close in progress 185 | case 1090: // shutdown in progress - connection is not permitted 186 | case 1092: // ORACLE instance terminated. Disconnection forced 187 | case 3113: // end-of-file on communication channel 188 | case 3114: // not connected to ORACLE 189 | case 3122: // attempt to close ORACLE-side window on user side 190 | case 3135: // connection lost contact 191 | case 12153: // TNS:not connected 192 | case 12203: // TNS:unable to connect to destination 193 | case 12500: // TNS:listener failed to start a dedicated server process 194 | case 12571: // TNS:packet writer failure 195 | case 27146: // post/wait initialization failed 196 | case 28511: // lost RPC connection to heterogeneous remote agent 197 | exceptionType = cxoOperationalErrorException; 198 | break; 199 | default: 200 | exceptionType = cxoDatabaseErrorException; 201 | break; 202 | } 203 | PyErr_SetObject(exceptionType, (PyObject*) error); 204 | Py_DECREF(error); 205 | return -1; 206 | } 207 | 208 | 209 | //----------------------------------------------------------------------------- 210 | // cxoError_raiseFromString() 211 | // Internal method for raising an exception given an error information 212 | // structure from DPI. Return -1 as a convenience to the caller. 213 | //----------------------------------------------------------------------------- 214 | PyObject *cxoError_raiseFromString(PyObject *exceptionType, 215 | const char *message) 216 | { 217 | cxoError *error; 218 | 219 | error = cxoError_newFromString(message); 220 | if (!error) 221 | return NULL; 222 | PyErr_SetObject(exceptionType, (PyObject*) error); 223 | Py_DECREF(error); 224 | return NULL; 225 | } 226 | 227 | 228 | //----------------------------------------------------------------------------- 229 | // cxoError_reduce() 230 | // Method provided for pickling/unpickling of Error objects. 231 | //----------------------------------------------------------------------------- 232 | static PyObject *cxoError_reduce(cxoError *error) 233 | { 234 | return Py_BuildValue("(O(OiIO))", Py_TYPE(error), error->message, 235 | error->code, error->offset, error->context); 236 | } 237 | 238 | 239 | //----------------------------------------------------------------------------- 240 | // cxoError_str() 241 | // Return a string representation of the error variable. 242 | //----------------------------------------------------------------------------- 243 | static PyObject *cxoError_str(cxoError *error) 244 | { 245 | Py_INCREF(error->message); 246 | return error->message; 247 | } 248 | 249 | 250 | //----------------------------------------------------------------------------- 251 | // declaration of methods 252 | //----------------------------------------------------------------------------- 253 | static PyMethodDef cxoErrorMethods[] = { 254 | { "__reduce__", (PyCFunction) cxoError_reduce, METH_NOARGS }, 255 | { NULL, NULL } 256 | }; 257 | 258 | 259 | //----------------------------------------------------------------------------- 260 | // declaration of members 261 | //----------------------------------------------------------------------------- 262 | static PyMemberDef cxoErrorMembers[] = { 263 | { "code", T_LONG, offsetof(cxoError, code), READONLY }, 264 | { "offset", T_UINT, offsetof(cxoError, offset), READONLY }, 265 | { "message", T_OBJECT, offsetof(cxoError, message), READONLY }, 266 | { "context", T_OBJECT, offsetof(cxoError, context), READONLY }, 267 | { "isrecoverable", T_BOOL, offsetof(cxoError, isRecoverable), READONLY }, 268 | { NULL } 269 | }; 270 | 271 | 272 | //----------------------------------------------------------------------------- 273 | // declaration of Python type 274 | //----------------------------------------------------------------------------- 275 | PyTypeObject cxoPyTypeError = { 276 | PyVarObject_HEAD_INIT(NULL, 0) 277 | .tp_name = "cx_Oracle._Error", 278 | .tp_basicsize = sizeof(cxoError), 279 | .tp_dealloc = (destructor) cxoError_free, 280 | .tp_str = (reprfunc) cxoError_str, 281 | .tp_flags = Py_TPFLAGS_DEFAULT, 282 | .tp_methods = cxoErrorMethods, 283 | .tp_members = cxoErrorMembers, 284 | .tp_new = cxoError_new 285 | }; 286 | -------------------------------------------------------------------------------- /src/cxoFuture.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoFuture.c 7 | // Defines the object used for managing behavior changes. This object permits 8 | // setting any attribute to any value but only tracks certain values. 9 | //----------------------------------------------------------------------------- 10 | 11 | #include "cxoModule.h" 12 | 13 | //----------------------------------------------------------------------------- 14 | // cxoFuture_free() 15 | // Free the future object and reset global. 16 | //----------------------------------------------------------------------------- 17 | static void cxoFuture_free(cxoFuture *obj) 18 | { 19 | Py_TYPE(obj)->tp_free((PyObject*) obj); 20 | cxoFutureObj = NULL; 21 | } 22 | 23 | 24 | //----------------------------------------------------------------------------- 25 | // cxoFuture_getAttr() 26 | // Retrieve an attribute on an object. 27 | //----------------------------------------------------------------------------- 28 | static PyObject *cxoFuture_getAttr(cxoFuture *obj, PyObject *nameObject) 29 | { 30 | Py_RETURN_NONE; 31 | } 32 | 33 | 34 | //----------------------------------------------------------------------------- 35 | // cxoFuture_setAttr() 36 | // Set an attribute on an object. 37 | //----------------------------------------------------------------------------- 38 | static int cxoFuture_setAttr(cxoFuture *obj, PyObject *nameObject, 39 | PyObject *value) 40 | { 41 | return 0; 42 | } 43 | 44 | 45 | //----------------------------------------------------------------------------- 46 | // Python type declaration 47 | //----------------------------------------------------------------------------- 48 | PyTypeObject cxoPyTypeFuture = { 49 | PyVarObject_HEAD_INIT(NULL, 0) 50 | .tp_name = "cx_Oracle.__future__", 51 | .tp_basicsize = sizeof(cxoFuture), 52 | .tp_dealloc = (destructor) cxoFuture_free, 53 | .tp_getattro = (getattrofunc) cxoFuture_getAttr, 54 | .tp_setattro = (setattrofunc) cxoFuture_setAttr, 55 | .tp_flags = Py_TPFLAGS_DEFAULT 56 | }; 57 | -------------------------------------------------------------------------------- /src/cxoJsonBuffer.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoJsonBuffer.c 7 | // Defines buffer structure and routines for populating JSON values. These 8 | // are used to translate Python objects (scalars, dictionaries and lists) into 9 | // JSON values stored in the database. 10 | //----------------------------------------------------------------------------- 11 | 12 | #include "cxoModule.h" 13 | 14 | #define CXO_JSON_ENCODING "UTF-8" 15 | 16 | //----------------------------------------------------------------------------- 17 | // cxoJsonBuffer_getBuffer() 18 | // Acquire a new buffer from the array of buffers. If one is not available, 19 | // more space is allocated in chunks. 20 | //----------------------------------------------------------------------------- 21 | static int cxoJsonBuffer_getBuffer(cxoJsonBuffer *buf, cxoBuffer **buffer) 22 | { 23 | cxoBuffer *tempBuffers; 24 | 25 | if (buf->numBuffers == buf->allocatedBuffers) { 26 | buf->allocatedBuffers += 16; 27 | tempBuffers = PyMem_Realloc(buf->buffers, 28 | buf->allocatedBuffers * sizeof(cxoBuffer)); 29 | if (!tempBuffers) { 30 | PyErr_NoMemory(); 31 | return -1; 32 | } 33 | buf->buffers = tempBuffers; 34 | } 35 | *buffer = &buf->buffers[buf->numBuffers++]; 36 | 37 | return 0; 38 | } 39 | 40 | 41 | //----------------------------------------------------------------------------- 42 | // cxoJsonBuffer_populateNode() 43 | // Populate a particular node with the value of the Python object. 44 | //----------------------------------------------------------------------------- 45 | static int cxoJsonBuffer_populateNode(cxoJsonBuffer *buf, dpiJsonNode *node, 46 | PyObject *value) 47 | { 48 | cxoTransformNum transformNum; 49 | PyObject *childValue, *key; 50 | cxoBuffer *tempBuffer; 51 | Py_ssize_t pos, size; 52 | dpiJsonArray *array; 53 | dpiJsonObject *obj; 54 | char message[250]; 55 | uint32_t i; 56 | 57 | // handle NULL values 58 | if (value == Py_None) { 59 | node->oracleTypeNum = DPI_ORACLE_TYPE_NONE; 60 | node->nativeTypeNum = DPI_NATIVE_TYPE_NULL; 61 | return 0; 62 | } 63 | 64 | // handle arrays 65 | if (PyList_Check(value)) { 66 | 67 | // initialize array 68 | node->oracleTypeNum = DPI_ORACLE_TYPE_JSON_ARRAY; 69 | node->nativeTypeNum = DPI_NATIVE_TYPE_JSON_ARRAY; 70 | array = &node->value->asJsonArray; 71 | array->numElements = (uint32_t) PyList_GET_SIZE(value); 72 | array->elements = PyMem_Calloc(array->numElements, 73 | sizeof(dpiJsonNode)); 74 | array->elementValues = PyMem_Calloc(array->numElements, 75 | sizeof(dpiDataBuffer)); 76 | if (!array->elements || !array->elementValues) { 77 | PyErr_NoMemory(); 78 | return -1; 79 | } 80 | 81 | // process each element of the array 82 | for (i = 0; i < array->numElements; i++) { 83 | childValue = PyList_GET_ITEM(value, i); 84 | array->elements[i].value = &array->elementValues[i]; 85 | if (cxoJsonBuffer_populateNode(buf, &array->elements[i], 86 | childValue) < 0) 87 | return -1; 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | // handle dictionaries 94 | if (PyDict_Check(value)) { 95 | 96 | // initialize object 97 | node->oracleTypeNum = DPI_ORACLE_TYPE_JSON_OBJECT; 98 | node->nativeTypeNum = DPI_NATIVE_TYPE_JSON_OBJECT; 99 | obj = &node->value->asJsonObject; 100 | size = PyDict_Size(value); 101 | if (size < 0) 102 | return -1; 103 | obj->numFields = (uint32_t) size; 104 | obj->fieldNames = PyMem_Calloc(obj->numFields, sizeof(char*)); 105 | obj->fieldNameLengths = PyMem_Calloc(obj->numFields, sizeof(uint32_t)); 106 | obj->fields = PyMem_Calloc(obj->numFields, sizeof(dpiJsonNode)); 107 | obj->fieldValues = PyMem_Calloc(obj->numFields, 108 | sizeof(dpiDataBuffer)); 109 | if (!obj->fieldNames || !obj->fieldNameLengths || !obj->fields || 110 | !obj->fieldValues) { 111 | PyErr_NoMemory(); 112 | return -1; 113 | } 114 | 115 | // process each entry in the dictionary 116 | i = 0; 117 | pos = 0; 118 | while (PyDict_Next(value, &pos, &key, &childValue)) { 119 | if (cxoJsonBuffer_getBuffer(buf, &tempBuffer) < 0) 120 | return -1; 121 | if (cxoBuffer_fromObject(tempBuffer, key, CXO_JSON_ENCODING) < 0) 122 | return -1; 123 | obj->fields[i].value = &obj->fieldValues[i]; 124 | obj->fieldNames[i] = (char*) tempBuffer->ptr; 125 | obj->fieldNameLengths[i] = tempBuffer->size; 126 | if (cxoJsonBuffer_populateNode(buf, &obj->fields[i], 127 | childValue) < 0) 128 | return -1; 129 | i++; 130 | } 131 | 132 | return 0; 133 | } 134 | 135 | // handle scalar values 136 | tempBuffer = NULL; 137 | transformNum = cxoTransform_getNumFromPythonValue(value, 1); 138 | switch (transformNum) { 139 | 140 | // strings and bytes must have a buffer made available for them to 141 | // store a reference to the object and the actual pointer and length; 142 | // numbers are converted to a string in order to prevent precision loss 143 | case CXO_TRANSFORM_STRING: 144 | case CXO_TRANSFORM_BINARY: 145 | case CXO_TRANSFORM_INT: 146 | case CXO_TRANSFORM_FLOAT: 147 | case CXO_TRANSFORM_DECIMAL: 148 | if (cxoJsonBuffer_getBuffer(buf, &tempBuffer) < 0) 149 | return -1; 150 | break; 151 | 152 | // swap CXO_TRANSFORM_DATETIME to CXO_TRANSFORM_TIMESTAMP to preserve 153 | // fractional seconds 154 | case CXO_TRANSFORM_DATETIME: 155 | transformNum = CXO_TRANSFORM_TIMESTAMP; 156 | break; 157 | 158 | // all other types do not need any special processing 159 | case CXO_TRANSFORM_BOOLEAN: 160 | case CXO_TRANSFORM_DATE: 161 | case CXO_TRANSFORM_TIMEDELTA: 162 | break; 163 | 164 | // any other type is not currently supported 165 | default: 166 | snprintf(message, sizeof(message), "Python type %s not supported.", 167 | Py_TYPE(value)->tp_name); 168 | cxoError_raiseFromString(cxoNotSupportedErrorException, message); 169 | return -1; 170 | } 171 | 172 | // transform the Python value into the Oracle value 173 | cxoTransform_getTypeInfo(transformNum, &node->oracleTypeNum, 174 | &node->nativeTypeNum); 175 | if (cxoTransform_fromPython(transformNum, &node->nativeTypeNum, value, 176 | node->value, tempBuffer, CXO_JSON_ENCODING, CXO_JSON_ENCODING, 177 | NULL, 0) < 0) 178 | return -1; 179 | 180 | return 0; 181 | } 182 | 183 | 184 | //----------------------------------------------------------------------------- 185 | // cxoJsonBuffer_freeNode() 186 | // Frees any arrays allocated earlier for the specified node. 187 | //----------------------------------------------------------------------------- 188 | static void cxoJsonBuffer_freeNode(dpiJsonNode *node) 189 | { 190 | dpiJsonArray *array; 191 | dpiJsonObject *obj; 192 | uint32_t i; 193 | 194 | switch (node->nativeTypeNum) { 195 | case DPI_NATIVE_TYPE_JSON_ARRAY: 196 | array = &node->value->asJsonArray; 197 | if (array->elements) { 198 | for (i = 0; i < array->numElements; i++) { 199 | if (array->elements[i].value) 200 | cxoJsonBuffer_freeNode(&array->elements[i]); 201 | } 202 | PyMem_Free(array->elements); 203 | array->elements = NULL; 204 | } 205 | if (array->elementValues) { 206 | PyMem_Free(array->elementValues); 207 | array->elementValues = NULL; 208 | } 209 | break; 210 | case DPI_NATIVE_TYPE_JSON_OBJECT: 211 | obj = &node->value->asJsonObject; 212 | if (obj->fields) { 213 | for (i = 0; i < obj->numFields; i++) { 214 | if (obj->fields[i].value) 215 | cxoJsonBuffer_freeNode(&obj->fields[i]); 216 | } 217 | PyMem_Free(obj->fields); 218 | obj->fields = NULL; 219 | } 220 | if (obj->fieldNames) { 221 | PyMem_Free(obj->fieldNames); 222 | obj->fieldNames = NULL; 223 | } 224 | if (obj->fieldNameLengths) { 225 | PyMem_Free(obj->fieldNameLengths); 226 | obj->fieldNameLengths = NULL; 227 | } 228 | if (obj->fieldValues) { 229 | PyMem_Free(obj->fieldValues); 230 | obj->fieldValues = NULL; 231 | } 232 | break; 233 | } 234 | } 235 | 236 | 237 | //----------------------------------------------------------------------------- 238 | // cxoJsonBuffer_free() 239 | // Frees any memory allocated for the JSON buffer. 240 | //----------------------------------------------------------------------------- 241 | void cxoJsonBuffer_free(cxoJsonBuffer *buf) 242 | { 243 | uint32_t i; 244 | 245 | if (buf->buffers) { 246 | for (i = 0; i < buf->numBuffers; i++) 247 | cxoBuffer_clear(&buf->buffers[i]); 248 | PyMem_Free(buf->buffers); 249 | buf->buffers = NULL; 250 | } 251 | cxoJsonBuffer_freeNode(&buf->topNode); 252 | } 253 | 254 | 255 | //----------------------------------------------------------------------------- 256 | // cxoJsonBuffer_fromObject() 257 | // Populate the JSON buffer from a Python object. 258 | //----------------------------------------------------------------------------- 259 | int cxoJsonBuffer_fromObject(cxoJsonBuffer *buf, PyObject *obj) 260 | { 261 | // initialize JSON buffer structure 262 | buf->topNode.value = &buf->topNodeBuffer; 263 | buf->allocatedBuffers = 0; 264 | buf->numBuffers = 0; 265 | buf->buffers = NULL; 266 | 267 | // populate the top level node 268 | return cxoJsonBuffer_populateNode(buf, &buf->topNode, obj); 269 | } 270 | -------------------------------------------------------------------------------- /src/cxoObjectAttr.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoObjectAttr.c 7 | // Defines the routines for handling attributes of Oracle types. 8 | //----------------------------------------------------------------------------- 9 | 10 | #include "cxoModule.h" 11 | 12 | //----------------------------------------------------------------------------- 13 | // cxoObjectAttr_initialize() 14 | // Initialize the new object attribute. 15 | //----------------------------------------------------------------------------- 16 | static int cxoObjectAttr_initialize(cxoObjectAttr *attr, 17 | cxoConnection *connection) 18 | { 19 | dpiObjectAttrInfo info; 20 | 21 | if (dpiObjectAttr_getInfo(attr->handle, &info) < 0) 22 | return cxoError_raiseAndReturnInt(); 23 | attr->transformNum = cxoTransform_getNumFromDataTypeInfo(&info.typeInfo); 24 | attr->dbType = cxoDbType_fromTransformNum(attr->transformNum); 25 | if (!attr->dbType) 26 | return -1; 27 | Py_INCREF(attr->dbType); 28 | attr->oracleTypeNum = info.typeInfo.oracleTypeNum; 29 | attr->name = PyUnicode_Decode(info.name, info.nameLength, 30 | connection->encodingInfo.encoding, NULL); 31 | if (!attr->name) 32 | return -1; 33 | if (info.typeInfo.objectType) { 34 | attr->objectType = cxoObjectType_new(connection, 35 | info.typeInfo.objectType); 36 | if (!attr->objectType) 37 | return -1; 38 | } 39 | 40 | return 0; 41 | } 42 | 43 | 44 | //----------------------------------------------------------------------------- 45 | // cxoObjectAttr_new() 46 | // Allocate a new object attribute. 47 | //----------------------------------------------------------------------------- 48 | cxoObjectAttr *cxoObjectAttr_new(cxoConnection *connection, 49 | dpiObjectAttr *handle) 50 | { 51 | cxoObjectAttr *attr; 52 | 53 | attr = (cxoObjectAttr*) 54 | cxoPyTypeObjectAttr.tp_alloc(&cxoPyTypeObjectAttr, 0); 55 | if (!attr) { 56 | dpiObjectAttr_release(handle); 57 | return NULL; 58 | } 59 | attr->handle = handle; 60 | if (cxoObjectAttr_initialize(attr, connection) < 0) { 61 | Py_DECREF(attr); 62 | return NULL; 63 | } 64 | 65 | return attr; 66 | } 67 | 68 | 69 | //----------------------------------------------------------------------------- 70 | // cxoObjectAttr_free() 71 | // Free the memory associated with an object attribute. 72 | //----------------------------------------------------------------------------- 73 | static void cxoObjectAttr_free(cxoObjectAttr *attr) 74 | { 75 | if (attr->handle) { 76 | dpiObjectAttr_release(attr->handle); 77 | attr->handle = NULL; 78 | } 79 | Py_CLEAR(attr->name); 80 | Py_CLEAR(attr->objectType); 81 | Py_CLEAR(attr->dbType); 82 | Py_TYPE(attr)->tp_free((PyObject*) attr); 83 | } 84 | 85 | 86 | //----------------------------------------------------------------------------- 87 | // cxoObjectAttr_getType() 88 | // Return the type associated with the attribute. This is either an object 89 | // type or one of the database type constants. 90 | //----------------------------------------------------------------------------- 91 | static PyObject *cxoObjectAttr_getType(cxoObjectAttr *attr, void *unused) 92 | { 93 | if (attr->objectType) { 94 | Py_INCREF(attr->objectType); 95 | return (PyObject*) attr->objectType; 96 | } 97 | 98 | Py_INCREF(attr->dbType); 99 | return (PyObject*) attr->dbType; 100 | } 101 | 102 | 103 | //----------------------------------------------------------------------------- 104 | // cxoObjectAttr_repr() 105 | // Return a string representation of the object attribute. 106 | //----------------------------------------------------------------------------- 107 | static PyObject *cxoObjectAttr_repr(cxoObjectAttr *attr) 108 | { 109 | PyObject *module, *name, *result; 110 | 111 | if (cxoUtils_getModuleAndName(Py_TYPE(attr), &module, &name) < 0) 112 | return NULL; 113 | result = cxoUtils_formatString("<%s.%s %s>", 114 | PyTuple_Pack(3, module, name, attr->name)); 115 | Py_DECREF(module); 116 | Py_DECREF(name); 117 | return result; 118 | } 119 | 120 | 121 | //----------------------------------------------------------------------------- 122 | // declaration of members 123 | //----------------------------------------------------------------------------- 124 | static PyMemberDef cxoMembers[] = { 125 | { "name", T_OBJECT, offsetof(cxoObjectAttr, name), READONLY }, 126 | { NULL } 127 | }; 128 | 129 | 130 | //----------------------------------------------------------------------------- 131 | // declaration of calculated members 132 | //----------------------------------------------------------------------------- 133 | static PyGetSetDef cxoCalcMembers[] = { 134 | { "type", (getter) cxoObjectAttr_getType, 0, 0, 0 }, 135 | { NULL } 136 | }; 137 | 138 | 139 | //----------------------------------------------------------------------------- 140 | // Python type declaration 141 | //----------------------------------------------------------------------------- 142 | PyTypeObject cxoPyTypeObjectAttr = { 143 | PyVarObject_HEAD_INIT(NULL, 0) 144 | .tp_name = "cx_Oracle.ObjectAttribute", 145 | .tp_basicsize = sizeof(cxoObjectAttr), 146 | .tp_dealloc = (destructor) cxoObjectAttr_free, 147 | .tp_repr = (reprfunc) cxoObjectAttr_repr, 148 | .tp_flags = Py_TPFLAGS_DEFAULT, 149 | .tp_members = cxoMembers, 150 | .tp_getset = cxoCalcMembers 151 | }; 152 | -------------------------------------------------------------------------------- /src/cxoSodaDoc.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoSodaDoc.c 7 | // Defines the routines for handling SODA documents. 8 | //----------------------------------------------------------------------------- 9 | 10 | #include "cxoModule.h" 11 | 12 | // forward declarations 13 | static PyObject *cxoSodaDoc_getContentAsString(cxoSodaDoc *doc, 14 | PyObject *args); 15 | 16 | 17 | //----------------------------------------------------------------------------- 18 | // cxoSodaDoc_new() 19 | // Create a new SODA document. 20 | //----------------------------------------------------------------------------- 21 | cxoSodaDoc *cxoSodaDoc_new(cxoSodaDatabase *db, dpiSodaDoc *handle) 22 | { 23 | cxoSodaDoc *doc; 24 | 25 | doc = (cxoSodaDoc*) cxoPyTypeSodaDoc.tp_alloc(&cxoPyTypeSodaDoc, 0); 26 | if (!doc) { 27 | dpiSodaDoc_release(handle); 28 | return NULL; 29 | } 30 | Py_INCREF(db); 31 | doc->db = db; 32 | doc->handle = handle; 33 | return doc; 34 | } 35 | 36 | 37 | //----------------------------------------------------------------------------- 38 | // cxoSodaDoc_free() 39 | // Free the memory associated with a SODA document. 40 | //----------------------------------------------------------------------------- 41 | static void cxoSodaDoc_free(cxoSodaDoc *doc) 42 | { 43 | if (doc->handle) { 44 | dpiSodaDoc_release(doc->handle); 45 | doc->handle = NULL; 46 | } 47 | Py_CLEAR(doc->db); 48 | Py_TYPE(doc)->tp_free((PyObject*) doc); 49 | } 50 | 51 | 52 | //----------------------------------------------------------------------------- 53 | // cxoSodaDoc_repr() 54 | // Return a string representation of a SODA document. 55 | //----------------------------------------------------------------------------- 56 | static PyObject *cxoSodaDoc_repr(cxoSodaDoc *doc) 57 | { 58 | PyObject *module, *name, *result, *keyObj; 59 | uint32_t keyLength; 60 | const char *key; 61 | 62 | if (dpiSodaDoc_getKey(doc->handle, &key, &keyLength) < 0) 63 | return cxoError_raiseAndReturnNull(); 64 | keyObj = PyUnicode_Decode(key, keyLength, 65 | doc->db->connection->encodingInfo.encoding, NULL); 66 | if (!keyObj) 67 | return NULL; 68 | if (cxoUtils_getModuleAndName(Py_TYPE(doc), &module, &name) < 0) { 69 | Py_DECREF(keyObj); 70 | return NULL; 71 | } 72 | result = cxoUtils_formatString("<%s.%s with key %s>", 73 | PyTuple_Pack(3, module, name, keyObj)); 74 | Py_DECREF(module); 75 | Py_DECREF(name); 76 | return result; 77 | } 78 | 79 | 80 | //----------------------------------------------------------------------------- 81 | // cxoSodaDoc_getCreatedOn() 82 | // Retrieve the time the SODA document was created, as a string in ISO 8601 83 | // format. 84 | //----------------------------------------------------------------------------- 85 | static PyObject *cxoSodaDoc_getCreatedOn(cxoSodaDoc *doc, void *unused) 86 | { 87 | uint32_t valueLength; 88 | const char *value; 89 | 90 | if (dpiSodaDoc_getCreatedOn(doc->handle, &value, &valueLength) < 0) 91 | return cxoError_raiseAndReturnNull(); 92 | if (valueLength > 0) 93 | return PyUnicode_Decode(value, valueLength, 94 | doc->db->connection->encodingInfo.encoding, NULL); 95 | Py_RETURN_NONE; 96 | } 97 | 98 | 99 | //----------------------------------------------------------------------------- 100 | // cxoSodaDoc_getKey() 101 | // Retrieve the key for the SODA document. 102 | //----------------------------------------------------------------------------- 103 | static PyObject *cxoSodaDoc_getKey(cxoSodaDoc *doc, void *unused) 104 | { 105 | uint32_t valueLength; 106 | const char *value; 107 | 108 | if (dpiSodaDoc_getKey(doc->handle, &value, &valueLength) < 0) 109 | return cxoError_raiseAndReturnNull(); 110 | if (valueLength > 0) 111 | return PyUnicode_Decode(value, valueLength, 112 | doc->db->connection->encodingInfo.encoding, NULL); 113 | Py_RETURN_NONE; 114 | } 115 | 116 | 117 | //----------------------------------------------------------------------------- 118 | // cxoSodaDoc_getLastModified() 119 | // Retrieve the time the SODA document was last modified, as a string in ISO 120 | // 8601 format. 121 | //----------------------------------------------------------------------------- 122 | static PyObject *cxoSodaDoc_getLastModified(cxoSodaDoc *doc, void *unused) 123 | { 124 | uint32_t valueLength; 125 | const char *value; 126 | 127 | if (dpiSodaDoc_getLastModified(doc->handle, &value, &valueLength) < 0) 128 | return cxoError_raiseAndReturnNull(); 129 | if (valueLength > 0) 130 | return PyUnicode_Decode(value, valueLength, 131 | doc->db->connection->encodingInfo.encoding, NULL); 132 | Py_RETURN_NONE; 133 | } 134 | 135 | 136 | //----------------------------------------------------------------------------- 137 | // cxoSodaDoc_getMediaType() 138 | // Retrieve the media type of the SODA document. 139 | //----------------------------------------------------------------------------- 140 | static PyObject *cxoSodaDoc_getMediaType(cxoSodaDoc *doc, void *unused) 141 | { 142 | uint32_t valueLength; 143 | const char *value; 144 | 145 | if (dpiSodaDoc_getMediaType(doc->handle, &value, &valueLength) < 0) 146 | return cxoError_raiseAndReturnNull(); 147 | if (valueLength > 0) 148 | return PyUnicode_Decode(value, valueLength, 149 | doc->db->connection->encodingInfo.encoding, NULL); 150 | Py_RETURN_NONE; 151 | } 152 | 153 | 154 | //----------------------------------------------------------------------------- 155 | // cxoSodaDoc_getVersion() 156 | // Retrieve the version for the SODA document. 157 | //----------------------------------------------------------------------------- 158 | static PyObject *cxoSodaDoc_getVersion(cxoSodaDoc *doc, void *unused) 159 | { 160 | uint32_t valueLength; 161 | const char *value; 162 | 163 | if (dpiSodaDoc_getVersion(doc->handle, &value, &valueLength) < 0) 164 | return cxoError_raiseAndReturnNull(); 165 | if (valueLength > 0) 166 | return PyUnicode_Decode(value, valueLength, 167 | doc->db->connection->encodingInfo.encoding, NULL); 168 | Py_RETURN_NONE; 169 | } 170 | 171 | 172 | //----------------------------------------------------------------------------- 173 | // cxoSodaDoc_getContent() 174 | // Get the content from the document and return a Python object. 175 | //----------------------------------------------------------------------------- 176 | static PyObject *cxoSodaDoc_getContent(cxoSodaDoc *doc, PyObject *args) 177 | { 178 | PyObject *str, *result; 179 | 180 | str = cxoSodaDoc_getContentAsString(doc, args); 181 | if (!str) 182 | return NULL; 183 | if (str == Py_None) 184 | return str; 185 | result = PyObject_CallFunctionObjArgs(cxoJsonLoadFunction, str, NULL); 186 | Py_DECREF(str); 187 | return result; 188 | } 189 | 190 | 191 | //----------------------------------------------------------------------------- 192 | // cxoSodaDoc_getContentAsBytes() 193 | // Get the content from the document and return a bytes object. 194 | //----------------------------------------------------------------------------- 195 | static PyObject *cxoSodaDoc_getContentAsBytes(cxoSodaDoc *doc, PyObject *args) 196 | { 197 | const char *content, *encoding; 198 | uint32_t contentLength; 199 | 200 | if (dpiSodaDoc_getContent(doc->handle, &content, &contentLength, 201 | &encoding) < 0) 202 | return cxoError_raiseAndReturnNull(); 203 | if (contentLength > 0) 204 | return PyBytes_FromStringAndSize(content, contentLength); 205 | Py_RETURN_NONE; 206 | } 207 | 208 | 209 | //----------------------------------------------------------------------------- 210 | // cxoSodaDoc_getContentAsString() 211 | // Get the content from the document and return a string. 212 | //----------------------------------------------------------------------------- 213 | static PyObject *cxoSodaDoc_getContentAsString(cxoSodaDoc *doc, PyObject *args) 214 | { 215 | const char *content, *encoding; 216 | uint32_t contentLength; 217 | 218 | if (dpiSodaDoc_getContent(doc->handle, &content, &contentLength, 219 | &encoding) < 0) 220 | return cxoError_raiseAndReturnNull(); 221 | if (contentLength > 0) 222 | return PyUnicode_Decode(content, contentLength, encoding, NULL); 223 | Py_RETURN_NONE; 224 | } 225 | 226 | 227 | //----------------------------------------------------------------------------- 228 | // declaration of methods 229 | //----------------------------------------------------------------------------- 230 | static PyMethodDef cxoMethods[] = { 231 | { "getContent", (PyCFunction) cxoSodaDoc_getContent, METH_NOARGS }, 232 | { "getContentAsBytes", (PyCFunction) cxoSodaDoc_getContentAsBytes, 233 | METH_NOARGS }, 234 | { "getContentAsString", (PyCFunction) cxoSodaDoc_getContentAsString, 235 | METH_NOARGS }, 236 | { NULL } 237 | }; 238 | 239 | 240 | //----------------------------------------------------------------------------- 241 | // declaration of calculated members 242 | //----------------------------------------------------------------------------- 243 | static PyGetSetDef cxoCalcMembers[] = { 244 | { "createdOn", (getter) cxoSodaDoc_getCreatedOn, 0, 0, 0 }, 245 | { "key", (getter) cxoSodaDoc_getKey, 0, 0, 0 }, 246 | { "lastModified", (getter) cxoSodaDoc_getLastModified, 0, 0, 0 }, 247 | { "mediaType", (getter) cxoSodaDoc_getMediaType, 0, 0, 0 }, 248 | { "version", (getter) cxoSodaDoc_getVersion, 0, 0, 0 }, 249 | { NULL } 250 | }; 251 | 252 | 253 | //----------------------------------------------------------------------------- 254 | // declaration of Python type 255 | //----------------------------------------------------------------------------- 256 | PyTypeObject cxoPyTypeSodaDoc = { 257 | PyVarObject_HEAD_INIT(NULL, 0) 258 | .tp_name = "cx_Oracle.SodaDoc", 259 | .tp_basicsize = sizeof(cxoSodaDoc), 260 | .tp_dealloc = (destructor) cxoSodaDoc_free, 261 | .tp_repr = (reprfunc) cxoSodaDoc_repr, 262 | .tp_flags = Py_TPFLAGS_DEFAULT, 263 | .tp_methods = cxoMethods, 264 | .tp_getset = cxoCalcMembers 265 | }; 266 | -------------------------------------------------------------------------------- /src/cxoSodaDocCursor.c: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 | //----------------------------------------------------------------------------- 4 | 5 | //----------------------------------------------------------------------------- 6 | // cxoSodaDocCursor.c 7 | // Defines the routines for handling SODA document cursors. These cursors 8 | // permit iterating over the documents that match the criteria that was 9 | // specified by the user. 10 | //----------------------------------------------------------------------------- 11 | 12 | #include "cxoModule.h" 13 | 14 | //----------------------------------------------------------------------------- 15 | // cxoSodaDocCursor_new() 16 | // Create a new SODA document cursor. 17 | //----------------------------------------------------------------------------- 18 | cxoSodaDocCursor *cxoSodaDocCursor_new(cxoSodaDatabase *db, 19 | dpiSodaDocCursor *handle) 20 | { 21 | cxoSodaDocCursor *cursor; 22 | 23 | cursor = (cxoSodaDocCursor*) 24 | cxoPyTypeSodaDocCursor.tp_alloc(&cxoPyTypeSodaDocCursor, 0); 25 | if (!cursor) { 26 | dpiSodaDocCursor_release(handle); 27 | return NULL; 28 | } 29 | Py_INCREF(db); 30 | cursor->db = db; 31 | cursor->handle = handle; 32 | return cursor; 33 | } 34 | 35 | 36 | //----------------------------------------------------------------------------- 37 | // cxoSodaDocCursor_free() 38 | // Free the memory associated with a SODA document cursor. 39 | //----------------------------------------------------------------------------- 40 | static void cxoSodaDocCursor_free(cxoSodaDocCursor *cursor) 41 | { 42 | if (cursor->handle) { 43 | dpiSodaDocCursor_release(cursor->handle); 44 | cursor->handle = NULL; 45 | } 46 | Py_CLEAR(cursor->db); 47 | Py_TYPE(cursor)->tp_free((PyObject*) cursor); 48 | } 49 | 50 | 51 | //----------------------------------------------------------------------------- 52 | // cxoSodaDocCursor_repr() 53 | // Return a string representation of a SODA document cursor. 54 | //----------------------------------------------------------------------------- 55 | static PyObject *cxoSodaDocCursor_repr(cxoSodaDocCursor *cursor) 56 | { 57 | PyObject *module, *name, *result; 58 | 59 | if (cxoUtils_getModuleAndName(Py_TYPE(cursor), &module, &name) < 0) 60 | return NULL; 61 | result = cxoUtils_formatString("<%s.%s>", PyTuple_Pack(2, module, name)); 62 | Py_DECREF(module); 63 | Py_DECREF(name); 64 | return result; 65 | } 66 | 67 | 68 | //----------------------------------------------------------------------------- 69 | // cxoSodaDocCursor_close() 70 | // Create a SODA collection and return it. 71 | //----------------------------------------------------------------------------- 72 | static PyObject *cxoSodaDocCursor_close(cxoSodaDocCursor *cursor, 73 | PyObject *args) 74 | { 75 | if (dpiSodaDocCursor_close(cursor->handle) < 0) 76 | return cxoError_raiseAndReturnNull(); 77 | Py_RETURN_NONE; 78 | } 79 | 80 | 81 | //----------------------------------------------------------------------------- 82 | // cxoSodaDocCursor_getIter() 83 | // Return a reference to the cursor which supports the iterator protocol. 84 | //----------------------------------------------------------------------------- 85 | static PyObject *cxoSodaDocCursor_getIter(cxoSodaDocCursor *cursor) 86 | { 87 | Py_INCREF(cursor); 88 | return (PyObject*) cursor; 89 | } 90 | 91 | 92 | //----------------------------------------------------------------------------- 93 | // cxoSodaDocCursor_getNext() 94 | // Return the next document from the cursor. 95 | //----------------------------------------------------------------------------- 96 | static PyObject *cxoSodaDocCursor_getNext(cxoSodaDocCursor *cursor) 97 | { 98 | dpiSodaDoc *handle; 99 | cxoSodaDoc *doc; 100 | int status; 101 | 102 | Py_BEGIN_ALLOW_THREADS 103 | status = dpiSodaDocCursor_getNext(cursor->handle, DPI_SODA_FLAGS_DEFAULT, 104 | &handle); 105 | Py_END_ALLOW_THREADS 106 | if (status < 0) 107 | return cxoError_raiseAndReturnNull(); 108 | if (!handle) 109 | return NULL; 110 | doc = cxoSodaDoc_new(cursor->db, handle); 111 | if (!doc) 112 | return NULL; 113 | return (PyObject*) doc; 114 | } 115 | 116 | 117 | //----------------------------------------------------------------------------- 118 | // declaration of methods 119 | //----------------------------------------------------------------------------- 120 | static PyMethodDef cxoMethods[] = { 121 | { "close", (PyCFunction) cxoSodaDocCursor_close, METH_NOARGS }, 122 | { NULL } 123 | }; 124 | 125 | 126 | //----------------------------------------------------------------------------- 127 | // Python type declarations 128 | //----------------------------------------------------------------------------- 129 | PyTypeObject cxoPyTypeSodaDocCursor = { 130 | PyVarObject_HEAD_INIT(NULL, 0) 131 | .tp_name = "cx_Oracle.SodaDocCursor", 132 | .tp_basicsize = sizeof(cxoSodaDocCursor), 133 | .tp_dealloc = (destructor) cxoSodaDocCursor_free, 134 | .tp_repr = (reprfunc) cxoSodaDocCursor_repr, 135 | .tp_flags = Py_TPFLAGS_DEFAULT, 136 | .tp_iter = (getiterfunc) cxoSodaDocCursor_getIter, 137 | .tp_iternext = (iternextfunc) cxoSodaDocCursor_getNext, 138 | .tp_methods = cxoMethods 139 | }; 140 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | This directory contains the test suite for cx_Oracle. 2 | 3 | 1. The schemas and SQL objects that are referenced in the test suite can be 4 | created by running the Python script [setup_test.py][1]. The script requires 5 | administrative privileges and will prompt for these credentials as well as 6 | the names of the schemas that will be created, unless a number of 7 | environment variables are set, as documented in the Python script 8 | [test_env.py][2]. Run the script using the following command: 9 | 10 | python setup_test.py 11 | 12 | Alternatively, the [SQL script][3] can be run directly via SQL\*Plus, which 13 | will always prompt for the names of the schemas that will be created. Run 14 | the script using the following command: 15 | 16 | sqlplus system/systempassword@hostname/servicename @sql/setup_test.sql 17 | 18 | 2. Run the test suite by issuing the following command in the top-level 19 | directory of your cx_Oracle installation: 20 | 21 | tox 22 | 23 | This will build the module in an independent environment and run the test 24 | suite using the module that was just built in that environment. 25 | Alternatively, you can use the currently installed build of cx_Oracle and 26 | run the following command instead: 27 | 28 | python -m unittest discover -v -s test 29 | 30 | You may also run each of the test scripts independently, as in: 31 | 32 | python test_1000_module.py 33 | 34 | 3. After running the test suite, the schemas can be dropped by running the 35 | Python script [drop_test.py][4]. The script requires administrative 36 | privileges and will prompt for these credentials as well as the names of 37 | the schemas that will be dropped, unless a number of environment variables 38 | are set, as documented in the Python script [test_env.py][2]. Run the 39 | script using the following command: 40 | 41 | python drop_test.py 42 | 43 | Alternatively, the [SQL script][5] can be run directly via SQL\*Plus, which 44 | will always prompt for the names of the schemas that will be dropped. Run 45 | the script using the following command: 46 | 47 | sqlplus system/systempassword@hostname/servicename @sql/drop_test.sql 48 | 49 | [1]: https://github.com/oracle/python-cx_Oracle/blob/main/test/setup_test.py 50 | [2]: https://github.com/oracle/python-cx_Oracle/blob/main/test/test_env.py 51 | [3]: https://github.com/oracle/python-cx_Oracle/blob/main/test/sql/setup_test.sql 52 | [4]: https://github.com/oracle/python-cx_Oracle/blob/main/test/drop_test.py 53 | [5]: https://github.com/oracle/python-cx_Oracle/blob/main/test/sql/drop_test.sql 54 | -------------------------------------------------------------------------------- /test/drop_test.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | #------------------------------------------------------------------------------ 6 | # drop_test.py 7 | # 8 | # Drops the database objects used by the test suite. 9 | #------------------------------------------------------------------------------ 10 | 11 | import cx_Oracle as oracledb 12 | import test_env 13 | 14 | def drop_tests(conn): 15 | print("Dropping test schemas...") 16 | test_env.run_sql_script(conn, "drop_test", 17 | main_user=test_env.get_main_user(), 18 | proxy_user=test_env.get_proxy_user()) 19 | 20 | if __name__ == "__main__": 21 | conn = oracledb.connect(test_env.get_admin_connect_string()) 22 | drop_tests(conn) 23 | print("Done.") 24 | -------------------------------------------------------------------------------- /test/setup_test.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | #------------------------------------------------------------------------------ 6 | # setup_test.py 7 | # 8 | # Creates users and populates their schemas with the tables and packages 9 | # necessary for the test suite. 10 | #------------------------------------------------------------------------------ 11 | 12 | import cx_Oracle as oracledb 13 | import drop_test 14 | import test_env 15 | 16 | # connect as administrative user (usually SYSTEM or ADMIN) 17 | conn = oracledb.connect(test_env.get_admin_connect_string()) 18 | 19 | # drop existing users and editions, if applicable 20 | drop_test.drop_tests(conn) 21 | 22 | # create test schemas 23 | print("Creating test schemas...") 24 | test_env.run_sql_script(conn, "setup_test", 25 | main_user=test_env.get_main_user(), 26 | main_password=test_env.get_main_password(), 27 | proxy_user=test_env.get_proxy_user(), 28 | proxy_password=test_env.get_proxy_password()) 29 | print("Done.") 30 | -------------------------------------------------------------------------------- /test/sql/drop_test.sql: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * Copyright 2019, Oracle and/or its affiliates. All rights reserved. 3 | *---------------------------------------------------------------------------*/ 4 | 5 | /*----------------------------------------------------------------------------- 6 | * drop_test.sql 7 | * Drops database objects used for cx_Oracle tests. 8 | * 9 | * Run this like: 10 | * sqlplus sys/syspassword@hostname/servicename as sysdba @drop_test 11 | *---------------------------------------------------------------------------*/ 12 | 13 | whenever sqlerror exit failure 14 | 15 | -- get parameters 16 | set echo off termout on feedback off verify off 17 | accept main_user char default pythontest - 18 | prompt "Name of main schema [pythontest]: " 19 | accept proxy_user char default pythontestproxy - 20 | prompt "Name of proxy schema [pythontestproxy]: " 21 | set feedback on 22 | 23 | -- perform work 24 | @@drop_test_exec.sql 25 | 26 | exit 27 | -------------------------------------------------------------------------------- /test/sql/drop_test_exec.sql: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. 3 | *---------------------------------------------------------------------------*/ 4 | 5 | /*----------------------------------------------------------------------------- 6 | * drop_test_exec.sql 7 | * This script performs the actual work of dropping the database schemas used 8 | * by the cx_Oracle test suite. It is called by the drop_test.sql and 9 | * setup_test.sql scripts after acquiring the necessary parameters and also by 10 | * the Python script drop_test.py. 11 | *---------------------------------------------------------------------------*/ 12 | 13 | begin 14 | 15 | for r in 16 | ( select username 17 | from dba_users 18 | where username in (upper('&main_user'), upper('&proxy_user')) 19 | ) loop 20 | execute immediate 'drop user ' || r.username || ' cascade'; 21 | end loop; 22 | 23 | end; 24 | / 25 | -------------------------------------------------------------------------------- /test/sql/setup_test.sql: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * Copyright 2019, Oracle and/or its affiliates. All rights reserved. 3 | *---------------------------------------------------------------------------*/ 4 | 5 | /*----------------------------------------------------------------------------- 6 | * setup_test.sql 7 | * Creates and populates schemas with the database objects used by the 8 | * cx_Oracle test suite. 9 | * 10 | * Run this like: 11 | * sqlplus sys/syspassword@hostname/servicename as sysdba @setup_test 12 | *---------------------------------------------------------------------------*/ 13 | 14 | whenever sqlerror exit failure 15 | 16 | -- get parameters 17 | set echo off termout on feedback off verify off 18 | accept main_user char default pythontest - 19 | prompt "Name of main schema [pythontest]: " 20 | accept main_password char prompt "Password for &main_user: " HIDE 21 | accept proxy_user char default pythontestproxy - 22 | prompt "Name of edition schema [pythontestproxy]: " 23 | accept proxy_password char prompt "Password for &proxy_user: " HIDE 24 | set feedback on 25 | 26 | -- perform work 27 | @@drop_test_exec.sql 28 | @@setup_test_exec.sql 29 | 30 | exit 31 | -------------------------------------------------------------------------------- /test/test_1000_module.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 1000 - Module for testing top-level module methods 7 | """ 8 | 9 | import datetime 10 | import time 11 | 12 | import cx_Oracle as oracledb 13 | import test_env 14 | 15 | class TestCase(test_env.BaseTestCase): 16 | requires_connection = False 17 | 18 | def test_1000_date_from_ticks(self): 19 | "1000 - test DateFromTicks()" 20 | today = datetime.datetime.today() 21 | timestamp = time.mktime(today.timetuple()) 22 | date = oracledb.DateFromTicks(timestamp) 23 | self.assertEqual(date, today.date()) 24 | 25 | def test_1001_future_obj(self): 26 | "1001 - test management of __future__ object" 27 | self.assertEqual(oracledb.__future__.dummy, None) 28 | oracledb.__future__.dummy = "Unimportant" 29 | self.assertEqual(oracledb.__future__.dummy, None) 30 | 31 | def test_1002_timestamp_from_ticks(self): 32 | "1002 - test TimestampFromTicks()" 33 | timestamp = time.mktime(datetime.datetime.today().timetuple()) 34 | today = datetime.datetime.fromtimestamp(timestamp) 35 | date = oracledb.TimestampFromTicks(timestamp) 36 | self.assertEqual(date, today) 37 | 38 | def test_1003_unsupported_functions(self): 39 | "1003 - test unsupported time functions" 40 | self.assertRaises(oracledb.NotSupportedError, oracledb.Time, 12, 0, 0) 41 | self.assertRaises(oracledb.NotSupportedError, oracledb.TimeFromTicks, 42 | 100) 43 | 44 | if __name__ == "__main__": 45 | test_env.run_test_cases() 46 | -------------------------------------------------------------------------------- /test/test_1300_cursor_var.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 1300 - Module for testing cursor variables 12 | """ 13 | 14 | import sys 15 | 16 | import cx_Oracle as oracledb 17 | import test_env 18 | 19 | class TestCase(test_env.BaseTestCase): 20 | 21 | def test_1300_bind_cursor(self): 22 | "1300 - test binding in a cursor" 23 | cursor = self.connection.cursor() 24 | self.assertEqual(cursor.description, None) 25 | self.cursor.execute(""" 26 | begin 27 | open :cursor for select 'X' StringValue from dual; 28 | end;""", 29 | cursor=cursor) 30 | varchar_ratio, nvarchar_ratio = test_env.get_charset_ratios() 31 | expected_value = [ 32 | ('STRINGVALUE', oracledb.DB_TYPE_CHAR, 1, varchar_ratio, None, 33 | None, True) 34 | ] 35 | self.assertEqual(cursor.description, expected_value) 36 | self.assertEqual(cursor.fetchall(), [('X',)]) 37 | 38 | def test_1301_bind_cursor_in_package(self): 39 | "1301 - test binding in a cursor from a package" 40 | cursor = self.connection.cursor() 41 | self.assertEqual(cursor.description, None) 42 | self.cursor.callproc("pkg_TestRefCursors.TestOutCursor", (2, cursor)) 43 | varchar_ratio, nvarchar_ratio = test_env.get_charset_ratios() 44 | expected_value = [ 45 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 46 | ('STRINGCOL', oracledb.DB_TYPE_VARCHAR, 20, 20 * varchar_ratio, 47 | None, None, False) 48 | ] 49 | self.assertEqual(cursor.description, expected_value) 50 | self.assertEqual(cursor.fetchall(), [(1, 'String 1'), (2, 'String 2')]) 51 | 52 | def test_1302_bind_self(self): 53 | "1302 - test that binding the cursor itself is not supported" 54 | cursor = self.connection.cursor() 55 | sql = """ 56 | begin 57 | open :pcursor for 58 | select 1 from dual; 59 | end;""" 60 | self.assertRaises(oracledb.DatabaseError, cursor.execute, sql, 61 | pcursor=cursor) 62 | 63 | def test_1303_execute_after_close(self): 64 | "1303 - test returning a ref cursor after closing it" 65 | out_cursor = self.connection.cursor() 66 | sql = """ 67 | begin 68 | open :pcursor for 69 | select IntCol 70 | from TestNumbers 71 | order by IntCol; 72 | end;""" 73 | self.cursor.execute(sql, pcursor=out_cursor) 74 | rows = out_cursor.fetchall() 75 | out_cursor.close() 76 | out_cursor = self.connection.cursor() 77 | self.cursor.execute(sql, pcursor=out_cursor) 78 | rows2 = out_cursor.fetchall() 79 | self.assertEqual(rows, rows2) 80 | 81 | def test_1304_fetch_cursor(self): 82 | "1304 - test fetching a cursor" 83 | self.cursor.execute(""" 84 | select 85 | IntCol, 86 | cursor(select IntCol + 1 from dual) CursorValue 87 | from TestNumbers 88 | order by IntCol""") 89 | expected_value = [ 90 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 91 | ('CURSORVALUE', oracledb.DB_TYPE_CURSOR, None, None, None, None, 92 | True) 93 | ] 94 | self.assertEqual(self.cursor.description, expected_value) 95 | for i in range(1, 11): 96 | number, cursor = self.cursor.fetchone() 97 | self.assertEqual(number, i) 98 | self.assertEqual(cursor.fetchall(), [(i + 1,)]) 99 | 100 | def test_1305_ref_cursor_binds(self): 101 | "1305 - test that ref cursor binds cannot use optimised path" 102 | ref_cursor = self.connection.cursor() 103 | sql = """ 104 | begin 105 | open :rcursor for 106 | select IntCol, StringCol 107 | from TestStrings where IntCol 108 | between :start_value and :end_value; 109 | end;""" 110 | self.cursor.execute(sql, rcursor=ref_cursor, start_value=2, 111 | end_value=4) 112 | expected_value = [ 113 | (2, 'String 2'), 114 | (3, 'String 3'), 115 | (4, 'String 4') 116 | ] 117 | rows = ref_cursor.fetchall() 118 | ref_cursor.close() 119 | self.assertEqual(rows, expected_value) 120 | ref_cursor = self.connection.cursor() 121 | self.cursor.execute(sql, rcursor=ref_cursor, start_value=5, 122 | end_value=6) 123 | expected_value = [ 124 | (5, 'String 5'), 125 | (6, 'String 6') 126 | ] 127 | rows = ref_cursor.fetchall() 128 | self.assertEqual(rows, expected_value) 129 | 130 | if __name__ == "__main__": 131 | test_env.run_test_cases() 132 | -------------------------------------------------------------------------------- /test/test_1500_types.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 1500 - Module for testing comparisons with database types and API types, 7 | including the synonyms retained for backwards compatibility. This module also 8 | tests for pickling/unpickling of database types and API types. 9 | """ 10 | 11 | import pickle 12 | 13 | import cx_Oracle as oracledb 14 | import test_env 15 | 16 | class TestCase(test_env.BaseTestCase): 17 | requires_connection = False 18 | 19 | def __test_compare(self, db_type, api_type): 20 | self.assertEqual(db_type, db_type) 21 | self.assertEqual(db_type, api_type) 22 | self.assertEqual(api_type, db_type) 23 | self.assertNotEqual(db_type, 5) 24 | self.assertNotEqual(db_type, oracledb.DB_TYPE_OBJECT) 25 | 26 | def __test_pickle(self, typ): 27 | self.assertIs(typ, pickle.loads(pickle.dumps(typ))) 28 | 29 | def test_1500_DB_TYPE_BFILE(self): 30 | "1500 - test oracledb.DB_TYPE_BFILE comparisons and pickling" 31 | self.assertEqual(oracledb.DB_TYPE_BFILE, oracledb.BFILE) 32 | self.__test_pickle(oracledb.DB_TYPE_BFILE) 33 | 34 | def test_1501_DB_TYPE_BINARY_DOUBLE(self): 35 | "1501 - test oracledb.DB_TYPE_BINARY_DOUBLE comparisons and pickling" 36 | self.__test_compare(oracledb.DB_TYPE_BINARY_DOUBLE, oracledb.NUMBER) 37 | self.assertEqual(oracledb.DB_TYPE_BINARY_DOUBLE, 38 | oracledb.NATIVE_FLOAT) 39 | self.__test_pickle(oracledb.DB_TYPE_BINARY_DOUBLE) 40 | 41 | def test_1502_DB_TYPE_BINARY_FLOAT(self): 42 | "1502 - test oracledb.DB_TYPE_BINARY_FLOAT comparisons and pickling" 43 | self.__test_compare(oracledb.DB_TYPE_BINARY_FLOAT, oracledb.NUMBER) 44 | self.__test_pickle(oracledb.DB_TYPE_BINARY_FLOAT) 45 | 46 | def test_1503_DB_TYPE_BINARY_INTEGER(self): 47 | "1503 - test oracledb.DB_TYPE_BINARY_INTEGER comparisons and pickling" 48 | self.__test_compare(oracledb.DB_TYPE_BINARY_INTEGER, oracledb.NUMBER) 49 | self.assertEqual(oracledb.DB_TYPE_BINARY_INTEGER, 50 | oracledb.NATIVE_INT) 51 | self.__test_pickle(oracledb.DB_TYPE_BINARY_INTEGER) 52 | 53 | def test_1504_DB_TYPE_BLOB(self): 54 | "1504 - test oracledb.DB_TYPE_BLOB comparisons and pickling" 55 | self.assertEqual(oracledb.DB_TYPE_BLOB, oracledb.BLOB) 56 | self.__test_pickle(oracledb.DB_TYPE_BLOB) 57 | 58 | def test_1505_DB_TYPE_BOOLEAN(self): 59 | "1505 - test oracledb.DB_TYPE_BOOLEAN comparisons and pickling" 60 | self.assertEqual(oracledb.DB_TYPE_BOOLEAN, oracledb.BOOLEAN) 61 | self.__test_pickle(oracledb.DB_TYPE_BOOLEAN) 62 | 63 | def test_1506_DB_TYPE_CHAR(self): 64 | "1506 - test oracledb.DB_TYPE_CHAR comparisons and pickling" 65 | self.__test_compare(oracledb.DB_TYPE_CHAR, oracledb.STRING) 66 | self.assertEqual(oracledb.DB_TYPE_CHAR, oracledb.FIXED_CHAR) 67 | self.__test_pickle(oracledb.DB_TYPE_CHAR) 68 | 69 | def test_1507_DB_TYPE_CLOB(self): 70 | "1507 - test oracledb.DB_TYPE_CLOB comparisons and pickling" 71 | self.assertEqual(oracledb.DB_TYPE_CLOB, oracledb.CLOB) 72 | self.__test_pickle(oracledb.DB_TYPE_CLOB) 73 | 74 | def test_1508_DB_TYPE_CURSOR(self): 75 | "1508 - test oracledb.DB_TYPE_CURSOR comparisons and pickling" 76 | self.assertEqual(oracledb.DB_TYPE_CURSOR, oracledb.CURSOR) 77 | self.__test_pickle(oracledb.DB_TYPE_CURSOR) 78 | 79 | def test_1509_DB_TYPE_DATE(self): 80 | "1509 - test oracledb.DB_TYPE_DATE comparisons and pickling" 81 | self.__test_compare(oracledb.DB_TYPE_DATE, oracledb.DATETIME) 82 | self.__test_pickle(oracledb.DB_TYPE_DATE) 83 | 84 | def test_1510_DB_TYPE_INTERVAL_DS(self): 85 | "1510 - test oracledb.DB_TYPE_INTERVAL_DS comparisons and pickling" 86 | self.assertEqual(oracledb.DB_TYPE_INTERVAL_DS, oracledb.INTERVAL) 87 | self.__test_pickle(oracledb.DB_TYPE_INTERVAL_DS) 88 | 89 | def test_1511_DB_TYPE_LONG(self): 90 | "1511 - test oracledb.DB_TYPE_LONG comparisons and pickling" 91 | self.__test_compare(oracledb.DB_TYPE_LONG, oracledb.STRING) 92 | self.assertEqual(oracledb.DB_TYPE_LONG, oracledb.LONG_STRING) 93 | self.__test_pickle(oracledb.DB_TYPE_LONG) 94 | 95 | def test_1512_DB_TYPE_LONG_RAW(self): 96 | "1512 - test oracledb.DB_TYPE_LONG_RAW comparisons and pickling" 97 | self.__test_compare(oracledb.DB_TYPE_LONG_RAW, oracledb.BINARY) 98 | self.assertEqual(oracledb.DB_TYPE_LONG_RAW, oracledb.LONG_BINARY) 99 | self.__test_pickle(oracledb.DB_TYPE_LONG_RAW) 100 | 101 | def test_1513_DB_TYPE_NCHAR(self): 102 | "1513 - test oracledb.DB_TYPE_NCHAR comparisons and pickling" 103 | self.__test_compare(oracledb.DB_TYPE_NCHAR, oracledb.STRING) 104 | self.assertEqual(oracledb.DB_TYPE_NCHAR, oracledb.FIXED_NCHAR) 105 | self.__test_pickle(oracledb.DB_TYPE_NCHAR) 106 | 107 | def test_1514_DB_TYPE_NCLOB(self): 108 | "1514 - test oracledb.DB_TYPE_NCLOB comparisons and pickling" 109 | self.assertEqual(oracledb.DB_TYPE_NCLOB, oracledb.NCLOB) 110 | self.__test_pickle(oracledb.DB_TYPE_NCLOB) 111 | 112 | def test_1515_DB_TYPE_NUMBER(self): 113 | "1515 - test oracledb.DB_TYPE_NUMBER comparisons and pickling" 114 | self.__test_compare(oracledb.DB_TYPE_NUMBER, oracledb.NUMBER) 115 | self.__test_pickle(oracledb.DB_TYPE_NUMBER) 116 | 117 | def test_1516_DB_TYPE_NVARCHAR(self): 118 | "1516 - test oracledb.DB_TYPE_NVARCHAR comparisons and pickling" 119 | self.__test_compare(oracledb.DB_TYPE_NVARCHAR, oracledb.STRING) 120 | self.assertEqual(oracledb.DB_TYPE_NVARCHAR, oracledb.NCHAR) 121 | self.__test_pickle(oracledb.DB_TYPE_NVARCHAR) 122 | 123 | def test_1517_DB_TYPE_OBJECT(self): 124 | "1517 - test oracledb.DB_TYPE_OBJECT comparisons and pickling" 125 | self.assertEqual(oracledb.DB_TYPE_OBJECT, oracledb.OBJECT) 126 | self.__test_pickle(oracledb.DB_TYPE_OBJECT) 127 | 128 | def test_1518_DB_TYPE_RAW(self): 129 | "1518 - test oracledb.DB_TYPE_RAW comparisons and pickling" 130 | self.__test_compare(oracledb.DB_TYPE_RAW, oracledb.BINARY) 131 | self.__test_pickle(oracledb.DB_TYPE_RAW) 132 | 133 | def test_1519_DB_TYPE_ROWID(self): 134 | "1519 - test oracledb.DB_TYPE_ROWID comparisons and pickling" 135 | self.__test_compare(oracledb.DB_TYPE_ROWID, oracledb.ROWID) 136 | self.__test_pickle(oracledb.DB_TYPE_ROWID) 137 | 138 | def test_1520_DB_TYPE_TIMESTAMP(self): 139 | "1520 - test oracledb.DB_TYPE_TIMESTAMP comparisons and pickling" 140 | self.__test_compare(oracledb.DB_TYPE_TIMESTAMP, oracledb.DATETIME) 141 | self.assertEqual(oracledb.DB_TYPE_TIMESTAMP, oracledb.TIMESTAMP) 142 | self.__test_pickle(oracledb.DB_TYPE_TIMESTAMP) 143 | 144 | def test_1521_DB_TYPE_TIMESTAMP_LTZ(self): 145 | "1521 - test oracledb.DB_TYPE_TIMESTAMP_LTZ comparisons and pickling" 146 | self.__test_compare(oracledb.DB_TYPE_TIMESTAMP_LTZ, oracledb.DATETIME) 147 | self.__test_pickle(oracledb.DB_TYPE_TIMESTAMP_LTZ) 148 | 149 | def test_1522_DB_TYPE_TIMESTAMP_TZ(self): 150 | "1522 - test oracledb.DB_TYPE_TIMESTAMP_TZ comparisons and pickling" 151 | self.__test_compare(oracledb.DB_TYPE_TIMESTAMP_TZ, oracledb.DATETIME) 152 | self.__test_pickle(oracledb.DB_TYPE_TIMESTAMP_TZ) 153 | 154 | def test_1523_DB_TYPE_VARCHAR(self): 155 | "1523 - test oracledb.DB_TYPE_VARCHAR comparisons and pickling" 156 | self.__test_compare(oracledb.DB_TYPE_VARCHAR, oracledb.STRING) 157 | self.__test_pickle(oracledb.DB_TYPE_VARCHAR) 158 | 159 | def test_1524_NUMBER(self): 160 | "1524 - test oracledb.NUMBER pickling" 161 | self.__test_pickle(oracledb.NUMBER) 162 | 163 | def test_1525_STRING(self): 164 | "1525 - test oracledb.STRING pickling" 165 | self.__test_pickle(oracledb.STRING) 166 | 167 | def test_1526_DATETIME(self): 168 | "1526 - test oracledb.DATETIME pickling" 169 | self.__test_pickle(oracledb.DATETIME) 170 | 171 | def test_1527_BINARY(self): 172 | "1527 - test oracledb.BINARY pickling" 173 | self.__test_pickle(oracledb.BINARY) 174 | 175 | def test_1528_ROWID(self): 176 | "1528 - test oracledb.ROWID pickling" 177 | self.__test_pickle(oracledb.ROWID) 178 | 179 | if __name__ == "__main__": 180 | test_env.run_test_cases() 181 | -------------------------------------------------------------------------------- /test/test_1700_error.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 1700 - Module for testing error objects 12 | """ 13 | 14 | import pickle 15 | 16 | import cx_Oracle as oracledb 17 | import test_env 18 | 19 | class TestCase(test_env.BaseTestCase): 20 | 21 | def test_1700_parse_error(self): 22 | "1700 - test parse error returns offset correctly" 23 | with self.assertRaises(oracledb.Error) as cm: 24 | self.cursor.execute("begin t_Missing := 5; end;") 25 | error_obj, = cm.exception.args 26 | self.assertEqual(error_obj.offset, 6) 27 | 28 | def test_1701_pickle_error(self): 29 | "1701 - test picking/unpickling an error object" 30 | with self.assertRaises(oracledb.Error) as cm: 31 | self.cursor.execute(""" 32 | begin 33 | raise_application_error(-20101, 'Test!'); 34 | end;""") 35 | error_obj, = cm.exception.args 36 | self.assertEqual(type(error_obj), oracledb._Error) 37 | self.assertTrue("Test!" in error_obj.message) 38 | self.assertEqual(error_obj.code, 20101) 39 | self.assertEqual(error_obj.offset, 0) 40 | self.assertTrue(isinstance(error_obj.isrecoverable, bool)) 41 | new_error_obj = pickle.loads(pickle.dumps(error_obj)) 42 | self.assertEqual(type(new_error_obj), oracledb._Error) 43 | self.assertTrue(new_error_obj.message == error_obj.message) 44 | self.assertTrue(new_error_obj.code == error_obj.code) 45 | self.assertTrue(new_error_obj.offset == error_obj.offset) 46 | self.assertTrue(new_error_obj.context == error_obj.context) 47 | self.assertTrue(new_error_obj.isrecoverable == error_obj.isrecoverable) 48 | 49 | if __name__ == "__main__": 50 | test_env.run_test_cases() 51 | -------------------------------------------------------------------------------- /test/test_1800_interval_var.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 1800 - Module for testing interval variables 12 | """ 13 | 14 | import datetime 15 | 16 | import cx_Oracle as oracledb 17 | import test_env 18 | 19 | class TestCase(test_env.BaseTestCase): 20 | 21 | def setUp(self): 22 | super().setUp() 23 | self.raw_data = [] 24 | self.data_by_key = {} 25 | for i in range(1, 11): 26 | delta = datetime.timedelta(days=i, hours=i, minutes=i * 2, 27 | seconds=i * 3) 28 | if i % 2 == 0: 29 | nullable_delta = None 30 | else: 31 | nullable_delta = datetime.timedelta(days=i + 5, hours=i + 2, 32 | minutes=i * 2 + 5, 33 | seconds=i * 3 + 5) 34 | data_tuple = (i, delta, nullable_delta) 35 | self.raw_data.append(data_tuple) 36 | self.data_by_key[i] = data_tuple 37 | 38 | def test_1800_bind_interval(self): 39 | "1800 - test binding in an interval" 40 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_INTERVAL_DS) 41 | value = datetime.timedelta(days=5, hours=5, minutes=10, seconds=15) 42 | self.cursor.execute(""" 43 | select * from TestIntervals 44 | where IntervalCol = :value""", 45 | value=value) 46 | self.assertEqual(self.cursor.fetchall(), [self.data_by_key[5]]) 47 | 48 | def test_1801_bind_null(self): 49 | "1801 - test binding in a null" 50 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_INTERVAL_DS) 51 | self.cursor.execute(""" 52 | select * from TestIntervals 53 | where IntervalCol = :value""", 54 | value=None) 55 | self.assertEqual(self.cursor.fetchall(), []) 56 | 57 | def test_1802_bind_out_set_input_sizes(self): 58 | "1802 - test binding out with set input sizes defined" 59 | bind_vars = \ 60 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_INTERVAL_DS) 61 | self.cursor.execute(""" 62 | begin 63 | :value := to_dsinterval('8 09:24:18.123789'); 64 | end;""") 65 | expected_value = datetime.timedelta(days=8, hours=9, minutes=24, 66 | seconds=18, microseconds=123789) 67 | self.assertEqual(bind_vars["value"].getvalue(), expected_value) 68 | 69 | def test_1803_bind_in_out_set_input_sizes(self): 70 | "1803 - test binding in/out with set input sizes defined" 71 | bind_vars = \ 72 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_INTERVAL_DS) 73 | self.cursor.execute(""" 74 | begin 75 | :value := :value + to_dsinterval('5 08:30:00'); 76 | end;""", 77 | value=datetime.timedelta(days=5, hours=2, minutes=15)) 78 | expected_value = datetime.timedelta(days=10, hours=10, minutes=45) 79 | self.assertEqual(bind_vars["value"].getvalue(), expected_value) 80 | 81 | def test_1804_bind_in_out_fractional_second(self): 82 | "1804 - test binding in/out with set input sizes defined" 83 | bind_vars = \ 84 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_INTERVAL_DS) 85 | self.cursor.execute(""" 86 | begin 87 | :value := :value + to_dsinterval('5 08:30:00'); 88 | end;""", 89 | value=datetime.timedelta(days=5, seconds=12.123789)) 90 | expected_value = datetime.timedelta(days=10, hours=8, minutes=30, 91 | seconds=12, microseconds=123789) 92 | self.assertEqual(bind_vars["value"].getvalue(), expected_value) 93 | 94 | def test_1805_bind_out_var(self): 95 | "1805 - test binding out with cursor.var() method" 96 | var = self.cursor.var(oracledb.DB_TYPE_INTERVAL_DS) 97 | self.cursor.execute(""" 98 | begin 99 | :value := to_dsinterval('15 18:35:45.586'); 100 | end;""", 101 | value=var) 102 | expected_value = datetime.timedelta(days=15, hours=18, minutes=35, 103 | seconds=45, milliseconds=586) 104 | self.assertEqual(var.getvalue(), expected_value) 105 | 106 | def test_1806_bind_in_out_var_direct_set(self): 107 | "1806 - test binding in/out with cursor.var() method" 108 | var = self.cursor.var(oracledb.DB_TYPE_INTERVAL_DS) 109 | var.setvalue(0, datetime.timedelta(days=1, minutes=50)) 110 | self.cursor.execute(""" 111 | begin 112 | :value := :value + to_dsinterval('8 05:15:00'); 113 | end;""", 114 | value=var) 115 | expected_value = datetime.timedelta(days=9, hours=6, minutes=5) 116 | self.assertEqual(var.getvalue(), expected_value) 117 | 118 | def test_1807_cursor_description(self): 119 | "1807 - test cursor description is accurate" 120 | self.cursor.execute("select * from TestIntervals") 121 | expected_value = [ 122 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 123 | ('INTERVALCOL', oracledb.DB_TYPE_INTERVAL_DS, None, None, 2, 6, 124 | False), 125 | ('NULLABLECOL', oracledb.DB_TYPE_INTERVAL_DS, None, None, 2, 6, 126 | True) 127 | ] 128 | self.assertEqual(self.cursor.description, expected_value) 129 | 130 | def test_1808_fetchall(self): 131 | "1808 - test that fetching all of the data returns the correct results" 132 | self.cursor.execute("select * From TestIntervals order by IntCol") 133 | self.assertEqual(self.cursor.fetchall(), self.raw_data) 134 | self.assertEqual(self.cursor.fetchall(), []) 135 | 136 | def test_1809_fetchmany(self): 137 | "1809 - test that fetching data in chunks returns the correct results" 138 | self.cursor.execute("select * From TestIntervals order by IntCol") 139 | self.assertEqual(self.cursor.fetchmany(3), self.raw_data[0:3]) 140 | self.assertEqual(self.cursor.fetchmany(2), self.raw_data[3:5]) 141 | self.assertEqual(self.cursor.fetchmany(4), self.raw_data[5:9]) 142 | self.assertEqual(self.cursor.fetchmany(3), self.raw_data[9:]) 143 | self.assertEqual(self.cursor.fetchmany(3), []) 144 | 145 | def test_1810_fetchone(self): 146 | "1810 - test that fetching a single row returns the correct results" 147 | self.cursor.execute(""" 148 | select * 149 | from TestIntervals 150 | where IntCol in (3, 4) 151 | order by IntCol""") 152 | self.assertEqual(self.cursor.fetchone(), self.data_by_key[3]) 153 | self.assertEqual(self.cursor.fetchone(), self.data_by_key[4]) 154 | self.assertEqual(self.cursor.fetchone(), None) 155 | 156 | def test_1811_bind_and_fetch_negative_interval(self): 157 | "1811 - test binding and fetching a negative interval" 158 | value = datetime.timedelta(days=-1, seconds=86314, microseconds=431152) 159 | self.cursor.execute("select :1 from dual", [value]) 160 | result, = self.cursor.fetchone() 161 | self.assertEqual(result, value) 162 | 163 | if __name__ == "__main__": 164 | test_env.run_test_cases() 165 | -------------------------------------------------------------------------------- /test/test_2000_long_var.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 2000 - Module for testing long and long raw variables 12 | """ 13 | 14 | import cx_Oracle as oracledb 15 | import test_env 16 | 17 | class TestCase(test_env.BaseTestCase): 18 | 19 | def __perform_test(self, typ): 20 | name_part = "Long" if typ is oracledb.DB_TYPE_LONG else "LongRaw" 21 | 22 | self.cursor.execute(f"truncate table Test{name_part}s") 23 | self.cursor.setinputsizes(long_string=typ) 24 | long_string = "" 25 | for i in range(1, 11): 26 | char = chr(ord('A') + i - 1) 27 | long_string += char * 25000 28 | if i % 3 == 1: 29 | bind_value = None 30 | else: 31 | if typ is oracledb.DB_TYPE_LONG_RAW: 32 | bind_value = long_string.encode() 33 | else: 34 | bind_value = long_string 35 | self.cursor.execute(f""" 36 | insert into Test{name_part}s ( 37 | IntCol, 38 | {name_part}Col 39 | ) values ( 40 | :integer_value, 41 | :long_string 42 | )""", 43 | integer_value=i, 44 | long_string=bind_value) 45 | self.connection.commit() 46 | self.cursor.execute(""" 47 | select * 48 | from Test%ss 49 | order by IntCol""" % name_part) 50 | long_string = "" 51 | for integer_value, fetched_value in self.cursor: 52 | char = chr(ord('A') + integer_value - 1) 53 | long_string += char * 25000 54 | if integer_value % 3 == 1: 55 | expected_value = None 56 | else: 57 | if typ is oracledb.DB_TYPE_LONG_RAW: 58 | expected_value = long_string.encode() 59 | else: 60 | expected_value = long_string 61 | if fetched_value is not None: 62 | self.assertEqual(len(fetched_value), integer_value * 25000) 63 | self.assertEqual(fetched_value, expected_value) 64 | 65 | def test_2000_longs(self): 66 | "2000 - test binding and fetching long data" 67 | self.__perform_test(oracledb.DB_TYPE_LONG) 68 | 69 | def test_2001_long_with_execute_many(self): 70 | "2001 - test binding long data with executemany()" 71 | data = [] 72 | self.cursor.execute("truncate table TestLongs") 73 | for i in range(5): 74 | char = chr(ord('A') + i) 75 | long_str = char * (32768 * (i + 1)) 76 | data.append((i + 1, long_str)) 77 | self.cursor.executemany("insert into TestLongs values (:1, :2)", data) 78 | self.connection.commit() 79 | self.cursor.execute("select * from TestLongs order by IntCol") 80 | fetched_data = self.cursor.fetchall() 81 | self.assertEqual(fetched_data, data) 82 | 83 | def test_2002_long_raws(self): 84 | "2002 - test binding and fetching long raw data" 85 | self.__perform_test(oracledb.DB_TYPE_LONG_RAW) 86 | 87 | def test_2003_long_cursor_description(self): 88 | "2003 - test cursor description is accurate for longs" 89 | self.cursor.execute("select * from TestLongs") 90 | expected_value = [ 91 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 92 | ('LONGCOL', oracledb.DB_TYPE_LONG, None, None, None, None, True) 93 | ] 94 | self.assertEqual(self.cursor.description, expected_value) 95 | 96 | def test_2004_long_raw_cursor_description(self): 97 | "2004 - test cursor description is accurate for long raws" 98 | self.cursor.execute("select * from TestLongRaws") 99 | expected_value = [ 100 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 101 | ('LONGRAWCOL', oracledb.DB_TYPE_LONG_RAW, None, None, None, None, 102 | True) 103 | ] 104 | self.assertEqual(self.cursor.description, expected_value) 105 | 106 | def test_2005_array_size_too_large(self): 107 | "2005 - test array size too large generates an exception" 108 | self.cursor.arraysize = 268435456 109 | self.assertRaises(oracledb.DatabaseError, self.cursor.execute, 110 | "select * from TestLongRaws") 111 | 112 | if __name__ == "__main__": 113 | test_env.run_test_cases() 114 | -------------------------------------------------------------------------------- /test/test_2100_nchar_var.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 2100 - Module for testing NCHAR variables 12 | """ 13 | 14 | import cx_Oracle as oracledb 15 | import test_env 16 | 17 | class TestCase(test_env.BaseTestCase): 18 | 19 | def setUp(self): 20 | super().setUp() 21 | self.raw_data = [] 22 | self.data_by_key = {} 23 | for i in range(1, 11): 24 | unicode_col = "Unicode \u3042 %d" % i 25 | fixed_char_col = ("Fixed Unicode %d" % i).ljust(40) 26 | if i % 2: 27 | nullable_col = "Nullable %d" % i 28 | else: 29 | nullable_col = None 30 | data_tuple = (i, unicode_col, fixed_char_col, nullable_col) 31 | self.raw_data.append(data_tuple) 32 | self.data_by_key[i] = data_tuple 33 | 34 | def test_2100_unicode_length(self): 35 | "2100 - test value length" 36 | return_value = self.cursor.var(int) 37 | self.cursor.execute(""" 38 | begin 39 | :retval := LENGTH(:value); 40 | end;""", 41 | value="InVal \u3042", 42 | retval=return_value) 43 | self.assertEqual(return_value.getvalue(), 7) 44 | 45 | def test_2101_bind_unicode(self): 46 | "2101 - test binding in a unicode" 47 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_NVARCHAR) 48 | self.cursor.execute(""" 49 | select * from TestUnicodes 50 | where UnicodeCol = :value""", 51 | value="Unicode \u3042 5") 52 | self.assertEqual(self.cursor.fetchall(), [self.data_by_key[5]]) 53 | 54 | def test_2102_bind_different_var(self): 55 | "2102 - test binding a different variable on second execution" 56 | retval_1 = self.cursor.var(oracledb.DB_TYPE_NVARCHAR, 30) 57 | retval_2 = self.cursor.var(oracledb.DB_TYPE_NVARCHAR, 30) 58 | self.cursor.execute(r"begin :retval := unistr('Called \3042'); end;", 59 | retval=retval_1) 60 | self.assertEqual(retval_1.getvalue(), "Called \u3042") 61 | self.cursor.execute("begin :retval := 'Called'; end;", retval=retval_2) 62 | self.assertEqual(retval_2.getvalue(), "Called") 63 | 64 | def test_2103_bind_unicode_after_number(self): 65 | "2103 - test binding in a string after setting input sizes to a number" 66 | unicode_val = self.cursor.var(oracledb.DB_TYPE_NVARCHAR) 67 | unicode_val.setvalue(0, "Unicode \u3042 6") 68 | self.cursor.setinputsizes(value=oracledb.NUMBER) 69 | self.cursor.execute(""" 70 | select * from TestUnicodes 71 | where UnicodeCol = :value""", 72 | value=unicode_val) 73 | self.assertEqual(self.cursor.fetchall(), [self.data_by_key[6]]) 74 | 75 | def test_2104_bind_unicode_array_direct(self): 76 | "2104 - test binding in a unicode array" 77 | return_value = self.cursor.var(oracledb.NUMBER) 78 | array = [r[1] for r in self.raw_data] 79 | array_var = self.cursor.arrayvar(oracledb.DB_TYPE_NVARCHAR, array) 80 | statement = """ 81 | begin 82 | :retval := pkg_TestUnicodeArrays.TestInArrays( 83 | :integer_value, :array); 84 | end;""" 85 | self.cursor.execute(statement, retval=return_value, integer_value=5, 86 | array=array_var) 87 | self.assertEqual(return_value.getvalue(), 116) 88 | array = ["Unicode - \u3042 %d" % i for i in range(15)] 89 | array_var = self.cursor.arrayvar(oracledb.DB_TYPE_NVARCHAR, array) 90 | self.cursor.execute(statement, integer_value=8, array=array_var) 91 | self.assertEqual(return_value.getvalue(), 208) 92 | 93 | def test_2105_bind_unicode_array_by_sizes(self): 94 | "2105 - test binding in a unicode array (with setinputsizes)" 95 | return_value = self.cursor.var(oracledb.NUMBER) 96 | self.cursor.setinputsizes(array = [oracledb.DB_TYPE_NVARCHAR, 10]) 97 | array = [r[1] for r in self.raw_data] 98 | self.cursor.execute(""" 99 | begin 100 | :retval := pkg_TestUnicodeArrays.TestInArrays(:integer_value, 101 | :array); 102 | end;""", 103 | retval=return_value, 104 | integer_value=6, 105 | array=array) 106 | self.assertEqual(return_value.getvalue(), 117) 107 | 108 | def test_2106_bind_unicode_array_by_var(self): 109 | "2106 - test binding in a unicode array (with arrayvar)" 110 | return_value = self.cursor.var(oracledb.NUMBER) 111 | array = self.cursor.arrayvar(oracledb.DB_TYPE_NVARCHAR, 10, 20) 112 | array.setvalue(0, [r[1] for r in self.raw_data]) 113 | self.cursor.execute(""" 114 | begin 115 | :retval := pkg_TestUnicodeArrays.TestInArrays(:integer_value, 116 | :array); 117 | end;""", 118 | retval=return_value, 119 | integer_value=7, 120 | array=array) 121 | self.assertEqual(return_value.getvalue(), 118) 122 | 123 | def test_2107_bind_in_out_unicode_array_by_var(self): 124 | "2107 - test binding in/out a unicode array (with arrayvar)" 125 | array = self.cursor.arrayvar(oracledb.DB_TYPE_NVARCHAR, 10, 100) 126 | original_data = [r[1] for r in self.raw_data] 127 | fmt = "Converted element \u3042 # %d originally had length %d" 128 | expected_data = [fmt % (i, len(original_data[i - 1])) \ 129 | for i in range(1, 6)] + original_data[5:] 130 | array.setvalue(0, original_data) 131 | self.cursor.execute(""" 132 | begin 133 | pkg_TestUnicodeArrays.TestInOutArrays(:numElems, :array); 134 | end;""", 135 | numElems = 5, 136 | array = array) 137 | self.assertEqual(array.getvalue(), expected_data) 138 | 139 | def test_2108_bind_out_unicode_array_by_var(self): 140 | "2108 - test binding out a unicode array (with arrayvar)" 141 | array = self.cursor.arrayvar(oracledb.DB_TYPE_NVARCHAR, 6, 100) 142 | fmt = "Test out element \u3042 # %d" 143 | expected_data = [fmt % i for i in range(1, 7)] 144 | self.cursor.execute(""" 145 | begin 146 | pkg_TestUnicodeArrays.TestOutArrays(:numElems, :array); 147 | end;""", 148 | numElems = 6, 149 | array = array) 150 | self.assertEqual(array.getvalue(), expected_data) 151 | 152 | def test_2109_bind_null(self): 153 | "2109 - test binding in a null" 154 | self.cursor.execute(""" 155 | select * from TestUnicodes 156 | where UnicodeCol = :value""", 157 | value = None) 158 | self.assertEqual(self.cursor.fetchall(), []) 159 | 160 | def test_2110_bind_out_set_input_sizes_by_type(self): 161 | "2110 - test binding out with set input sizes defined (by type)" 162 | bind_vars = self.cursor.setinputsizes(value=oracledb.DB_TYPE_NVARCHAR) 163 | self.cursor.execute(r""" 164 | begin 165 | :value := unistr('TSI \3042'); 166 | end;""") 167 | self.assertEqual(bind_vars["value"].getvalue(), "TSI \u3042") 168 | 169 | def test_2111_bind_in_out_set_input_sizes_by_type(self): 170 | "2111 - test binding in/out with set input sizes defined (by type)" 171 | bind_vars = self.cursor.setinputsizes(value=oracledb.DB_TYPE_NVARCHAR) 172 | self.cursor.execute(r""" 173 | begin 174 | :value := :value || unistr(' TSI \3042'); 175 | end;""", 176 | value = "InVal \u3041") 177 | self.assertEqual(bind_vars["value"].getvalue(), 178 | "InVal \u3041 TSI \u3042") 179 | 180 | def test_2112_bind_out_var(self): 181 | "2112 - test binding out with cursor.var() method" 182 | var = self.cursor.var(oracledb.DB_TYPE_NVARCHAR) 183 | self.cursor.execute(r""" 184 | begin 185 | :value := unistr('TSI (VAR) \3042'); 186 | end;""", 187 | value=var) 188 | self.assertEqual(var.getvalue(), "TSI (VAR) \u3042") 189 | 190 | def test_2113_bind_in_out_var_direct_set(self): 191 | "2113 - test binding in/out with cursor.var() method" 192 | var = self.cursor.var(oracledb.DB_TYPE_NVARCHAR) 193 | var.setvalue(0, "InVal \u3041") 194 | self.cursor.execute(r""" 195 | begin 196 | :value := :value || unistr(' TSI (VAR) \3042'); 197 | end;""", 198 | value = var) 199 | self.assertEqual(var.getvalue(), "InVal \u3041 TSI (VAR) \u3042") 200 | 201 | def test_2114_cursor_description(self): 202 | "2114 - test cursor description is accurate" 203 | self.cursor.execute("select * from TestUnicodes") 204 | varchar_ratio, nvarchar_ratio = test_env.get_charset_ratios() 205 | expected_value = [ 206 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 207 | ('UNICODECOL', oracledb.DB_TYPE_NVARCHAR, 20, 20 * nvarchar_ratio, 208 | None, None, False), 209 | ('FIXEDUNICODECOL', oracledb.DB_TYPE_NCHAR, 40, 210 | 40 * nvarchar_ratio, None, None, False), 211 | ('NULLABLECOL', oracledb.DB_TYPE_NVARCHAR, 50, 50 * nvarchar_ratio, 212 | None, None, True) 213 | ] 214 | self.assertEqual(self.cursor.description, expected_value) 215 | 216 | def test_2115_fetchall(self): 217 | "2115 - test that fetching all of the data returns the correct results" 218 | self.cursor.execute("select * From TestUnicodes order by IntCol") 219 | self.assertEqual(self.cursor.fetchall(), self.raw_data) 220 | self.assertEqual(self.cursor.fetchall(), []) 221 | 222 | def test_2116_fetchmany(self): 223 | "2116 - test that fetching data in chunks returns the correct results" 224 | self.cursor.execute("select * From TestUnicodes order by IntCol") 225 | self.assertEqual(self.cursor.fetchmany(3), self.raw_data[0:3]) 226 | self.assertEqual(self.cursor.fetchmany(2), self.raw_data[3:5]) 227 | self.assertEqual(self.cursor.fetchmany(4), self.raw_data[5:9]) 228 | self.assertEqual(self.cursor.fetchmany(3), self.raw_data[9:]) 229 | self.assertEqual(self.cursor.fetchmany(3), []) 230 | 231 | def test_2117_fetchone(self): 232 | "2117 - test that fetching a single row returns the correct results" 233 | self.cursor.execute(""" 234 | select * 235 | from TestUnicodes 236 | where IntCol in (3, 4) 237 | order by IntCol""") 238 | self.assertEqual(self.cursor.fetchone(), self.data_by_key[3]) 239 | self.assertEqual(self.cursor.fetchone(), self.data_by_key[4]) 240 | self.assertEqual(self.cursor.fetchone(), None) 241 | 242 | if __name__ == "__main__": 243 | test_env.run_test_cases() 244 | -------------------------------------------------------------------------------- /test/test_2600_timestamp_var.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 2600 - Module for testing timestamp variables 12 | """ 13 | 14 | import time 15 | 16 | import cx_Oracle as oracledb 17 | import test_env 18 | 19 | class TestCase(test_env.BaseTestCase): 20 | 21 | def setUp(self): 22 | super().setUp() 23 | self.raw_data = [] 24 | self.data_by_key = {} 25 | for i in range(1, 11): 26 | time_tuple = (2002, 12, 9, 0, 0, 0, 0, 0, -1) 27 | time_in_ticks = time.mktime(time_tuple) + i * 86400 28 | date_value = oracledb.TimestampFromTicks(int(time_in_ticks)) 29 | str_value = str(i * 50) 30 | fsecond = int(str_value + "0" * (6 - len(str_value))) 31 | date_col = oracledb.Timestamp(date_value.year, date_value.month, 32 | date_value.day, date_value.hour, 33 | date_value.minute, i * 2, fsecond) 34 | if i % 2: 35 | time_in_ticks = time.mktime(time_tuple) + i * 86400 + 86400 36 | date_value = oracledb.TimestampFromTicks(int(time_in_ticks)) 37 | str_value = str(i * 125) 38 | fsecond = int(str_value + "0" * (6 - len(str_value))) 39 | nullable_col = oracledb.Timestamp(date_value.year, 40 | date_value.month, 41 | date_value.day, 42 | date_value.hour, 43 | date_value.minute, i * 3, 44 | fsecond) 45 | else: 46 | nullable_col = None 47 | data_tuple = (i, date_col, nullable_col) 48 | self.raw_data.append(data_tuple) 49 | self.data_by_key[i] = data_tuple 50 | 51 | def test_2600_bind_timestamp(self): 52 | "2600 - test binding in a timestamp" 53 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_TIMESTAMP) 54 | self.cursor.execute(""" 55 | select * from TestTimestamps 56 | where TimestampCol = :value""", 57 | value=oracledb.Timestamp(2002, 12, 14, 0, 0, 10, 250000)) 58 | self.assertEqual(self.cursor.fetchall(), [self.data_by_key[5]]) 59 | 60 | def test_2601_bind_null(self): 61 | "2601 - test binding in a null" 62 | self.cursor.setinputsizes(value=oracledb.DB_TYPE_TIMESTAMP) 63 | self.cursor.execute(""" 64 | select * from TestTimestamps 65 | where TimestampCol = :value""", 66 | value=None) 67 | self.assertEqual(self.cursor.fetchall(), []) 68 | 69 | def test_2602_bind_out_set_input_sizes(self): 70 | "2602 - test binding out with set input sizes defined" 71 | bind_vars = self.cursor.setinputsizes(value=oracledb.DB_TYPE_TIMESTAMP) 72 | self.cursor.execute(""" 73 | begin 74 | :value := to_timestamp('20021209', 'YYYYMMDD'); 75 | end;""") 76 | self.assertEqual(bind_vars["value"].getvalue(), 77 | oracledb.Timestamp(2002, 12, 9)) 78 | 79 | def test_2603_bind_in_out_set_input_sizes(self): 80 | "2603 - test binding in/out with set input sizes defined" 81 | bind_vars = self.cursor.setinputsizes(value=oracledb.DB_TYPE_TIMESTAMP) 82 | self.cursor.execute(""" 83 | begin 84 | :value := :value + 5.25; 85 | end;""", 86 | value = oracledb.Timestamp(2002, 12, 12, 10, 0, 0)) 87 | self.assertEqual(bind_vars["value"].getvalue(), 88 | oracledb.Timestamp(2002, 12, 17, 16, 0, 0)) 89 | 90 | def test_2604_bind_out_var(self): 91 | "2604 - test binding out with cursor.var() method" 92 | var = self.cursor.var(oracledb.DB_TYPE_TIMESTAMP) 93 | self.cursor.execute(""" 94 | begin 95 | :value := to_date('20021231 12:31:00', 96 | 'YYYYMMDD HH24:MI:SS'); 97 | end;""", 98 | value=var) 99 | self.assertEqual(var.getvalue(), 100 | oracledb.Timestamp(2002, 12, 31, 12, 31, 0)) 101 | 102 | def test_2605_bind_in_out_var_direct_set(self): 103 | "2605 - test binding in/out with cursor.var() method" 104 | var = self.cursor.var(oracledb.DB_TYPE_TIMESTAMP) 105 | var.setvalue(0, oracledb.Timestamp(2002, 12, 9, 6, 0, 0)) 106 | self.cursor.execute(""" 107 | begin 108 | :value := :value + 5.25; 109 | end;""", 110 | value = var) 111 | self.assertEqual(var.getvalue(), 112 | oracledb.Timestamp(2002, 12, 14, 12, 0, 0)) 113 | 114 | def test_2606_cursor_description(self): 115 | "2606 - test cursor description is accurate" 116 | self.cursor.execute("select * from TestTimestamps") 117 | expected_value = [ 118 | ('INTCOL', oracledb.DB_TYPE_NUMBER, 10, None, 9, 0, False), 119 | ('TIMESTAMPCOL', oracledb.DB_TYPE_TIMESTAMP, 23, None, 0, 6, 120 | False), 121 | ('NULLABLECOL', oracledb.DB_TYPE_TIMESTAMP, 23, None, 0, 6, True) 122 | ] 123 | self.assertEqual(self.cursor.description, expected_value) 124 | 125 | def test_2607_fetchall(self): 126 | "2607 - test that fetching all of the data returns the correct results" 127 | self.cursor.execute("select * From TestTimestamps order by IntCol") 128 | self.assertEqual(self.cursor.fetchall(), self.raw_data) 129 | self.assertEqual(self.cursor.fetchall(), []) 130 | 131 | def test_2608_fetchmany(self): 132 | "2608 - test that fetching data in chunks returns the correct results" 133 | self.cursor.execute("select * From TestTimestamps order by IntCol") 134 | self.assertEqual(self.cursor.fetchmany(3), self.raw_data[0:3]) 135 | self.assertEqual(self.cursor.fetchmany(2), self.raw_data[3:5]) 136 | self.assertEqual(self.cursor.fetchmany(4), self.raw_data[5:9]) 137 | self.assertEqual(self.cursor.fetchmany(3), self.raw_data[9:]) 138 | self.assertEqual(self.cursor.fetchmany(3), []) 139 | 140 | def test_2609_fetchone(self): 141 | "2609 - test that fetching a single row returns the correct results" 142 | self.cursor.execute(""" 143 | select * 144 | from TestTimestamps 145 | where IntCol in (3, 4) 146 | order by IntCol""") 147 | self.assertEqual(self.cursor.fetchone(), self.data_by_key[3]) 148 | self.assertEqual(self.cursor.fetchone(), self.data_by_key[4]) 149 | self.assertEqual(self.cursor.fetchone(), None) 150 | 151 | if __name__ == "__main__": 152 | test_env.run_test_cases() 153 | -------------------------------------------------------------------------------- /test/test_2800_bulk_aq.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 2800 - Module for testing AQ Bulk enqueue/dequeue 7 | """ 8 | 9 | import decimal 10 | import threading 11 | 12 | import cx_Oracle as oracledb 13 | import test_env 14 | 15 | RAW_QUEUE_NAME = "TEST_RAW_QUEUE" 16 | RAW_PAYLOAD_DATA = [ 17 | "The first message", 18 | "The second message", 19 | "The third message", 20 | "The fourth message", 21 | "The fifth message", 22 | "The sixth message", 23 | "The seventh message", 24 | "The eighth message", 25 | "The ninth message", 26 | "The tenth message", 27 | "The eleventh message", 28 | "The twelfth and final message" 29 | ] 30 | 31 | class TestCase(test_env.BaseTestCase): 32 | 33 | def __deq_in_thread(self, results): 34 | connection = test_env.get_connection(threaded=True) 35 | queue = connection.queue(RAW_QUEUE_NAME) 36 | queue.deqoptions.wait = 10 37 | queue.deqoptions.navigation = oracledb.DEQ_FIRST_MSG 38 | while len(results) < len(RAW_PAYLOAD_DATA): 39 | messages = queue.deqmany(5) 40 | if not messages: 41 | break 42 | for m in messages: 43 | results.append(m.payload.decode(connection.encoding)) 44 | connection.commit() 45 | 46 | def __get_and_clear_raw_queue(self): 47 | queue = self.connection.queue(RAW_QUEUE_NAME) 48 | queue.deqoptions.wait = oracledb.DEQ_NO_WAIT 49 | queue.deqoptions.navigation = oracledb.DEQ_FIRST_MSG 50 | while queue.deqone(): 51 | pass 52 | self.connection.commit() 53 | return queue 54 | 55 | def test_2800_enq_and_deq(self): 56 | "2800 - test bulk enqueue and dequeue" 57 | queue = self.__get_and_clear_raw_queue() 58 | messages = [self.connection.msgproperties(payload=d) \ 59 | for d in RAW_PAYLOAD_DATA] 60 | queue.enqmany(messages) 61 | messages = queue.deqmany(len(RAW_PAYLOAD_DATA)) 62 | data = [m.payload.decode(self.connection.encoding) for m in messages] 63 | self.connection.commit() 64 | self.assertEqual(data, RAW_PAYLOAD_DATA) 65 | 66 | def test_2801_dequeue_empty(self): 67 | "2801 - test empty bulk dequeue" 68 | queue = self.__get_and_clear_raw_queue() 69 | messages = queue.deqmany(5) 70 | self.connection.commit() 71 | self.assertEqual(messages, []) 72 | 73 | def test_2802_deq_with_wait(self): 74 | "2802 - test bulk dequeue with wait" 75 | queue = self.__get_and_clear_raw_queue() 76 | results = [] 77 | thread = threading.Thread(target=self.__deq_in_thread, args=(results,)) 78 | thread.start() 79 | messages = [self.connection.msgproperties(payload=d) \ 80 | for d in RAW_PAYLOAD_DATA] 81 | queue.enqoptions.visibility = oracledb.ENQ_IMMEDIATE 82 | queue.enqmany(messages) 83 | thread.join() 84 | self.assertEqual(results, RAW_PAYLOAD_DATA) 85 | 86 | def test_2803_enq_and_deq_multiple_times(self): 87 | "2803 - test enqueue and dequeue multiple times" 88 | queue = self.__get_and_clear_raw_queue() 89 | data_to_enqueue = RAW_PAYLOAD_DATA 90 | for num in (2, 6, 4): 91 | messages = [self.connection.msgproperties(payload=d) \ 92 | for d in data_to_enqueue[:num]] 93 | data_to_enqueue = data_to_enqueue[num:] 94 | queue.enqmany(messages) 95 | self.connection.commit() 96 | all_data = [] 97 | for num in (3, 5, 10): 98 | messages = queue.deqmany(num) 99 | all_data.extend(m.payload.decode(self.connection.encoding) \ 100 | for m in messages) 101 | self.connection.commit() 102 | self.assertEqual(all_data, RAW_PAYLOAD_DATA) 103 | 104 | def test_2804_enq_and_deq_visibility(self): 105 | "2804 - test visibility option for enqueue and dequeue" 106 | queue = self.__get_and_clear_raw_queue() 107 | 108 | # first test with ENQ_ON_COMMIT (commit required) 109 | queue.enqoptions.visibility = oracledb.ENQ_ON_COMMIT 110 | props1 = self.connection.msgproperties(payload="A first message") 111 | props2 = self.connection.msgproperties(payload="A second message") 112 | queue.enqmany([props1, props2]) 113 | other_connection = test_env.get_connection() 114 | other_queue = other_connection.queue(RAW_QUEUE_NAME) 115 | other_queue.deqoptions.wait = oracledb.DEQ_NO_WAIT 116 | other_queue.deqoptions.visibility = oracledb.DEQ_ON_COMMIT 117 | messages = other_queue.deqmany(5) 118 | self.assertEqual(len(messages), 0) 119 | self.connection.commit() 120 | messages = other_queue.deqmany(5) 121 | self.assertEqual(len(messages), 2) 122 | other_connection.rollback() 123 | 124 | # second test with ENQ_IMMEDIATE (no commit required) 125 | queue.enqoptions.visibility = oracledb.ENQ_IMMEDIATE 126 | other_queue.deqoptions.visibility = oracledb.DEQ_IMMEDIATE 127 | queue.enqmany([props1, props2]) 128 | messages = other_queue.deqmany(5) 129 | self.assertEqual(len(messages), 4) 130 | other_connection.rollback() 131 | messages = other_queue.deqmany(5) 132 | self.assertEqual(len(messages), 0) 133 | 134 | def test_2806_verify_msgid(self): 135 | "2806 - verify that the msgid property is returned correctly" 136 | queue = self.__get_and_clear_raw_queue() 137 | messages = [self.connection.msgproperties(payload=d) \ 138 | for d in RAW_PAYLOAD_DATA] 139 | queue.enqmany(messages) 140 | self.cursor.execute("select msgid from raw_queue_tab") 141 | actual_msgids = set(m for m, in self.cursor) 142 | msgids = set(m.msgid for m in messages) 143 | self.assertEqual(msgids, actual_msgids) 144 | messages = queue.deqmany(len(RAW_PAYLOAD_DATA)) 145 | msgids = set(m.msgid for m in messages) 146 | self.assertEqual(msgids, actual_msgids) 147 | 148 | if __name__ == "__main__": 149 | test_env.run_test_cases() 150 | -------------------------------------------------------------------------------- /test/test_2900_rowid.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 2900 - Module for testing Rowids 7 | """ 8 | 9 | import cx_Oracle as oracledb 10 | import test_env 11 | 12 | class TestCase(test_env.BaseTestCase): 13 | 14 | def __test_select_rowids(self, table_name): 15 | self.cursor.execute("select rowid, IntCol from %s""" % table_name) 16 | rowid_dict = dict(self.cursor) 17 | sql = "select IntCol from %s where rowid = :val" % table_name 18 | for rowid, int_val in rowid_dict.items(): 19 | self.cursor.execute(sql, val = rowid) 20 | rows = self.cursor.fetchall() 21 | self.assertEqual(len(rows), 1) 22 | self.assertEqual(rows[0][0], int_val) 23 | 24 | def test_2900_select_rowids_regular(self): 25 | "2900 - test selecting all rowids from a regular table" 26 | self.__test_select_rowids("TestNumbers") 27 | 28 | def test_2901_select_rowids_index_organised(self): 29 | "2901 - test selecting all rowids from an index organised table" 30 | self.__test_select_rowids("TestUniversalRowids") 31 | 32 | def test_2902_insert_invalid_rowid(self): 33 | "2902 - test inserting an invalid rowid" 34 | sql = "insert into TestRowids (IntCol, RowidCol) values (1, :rid)" 35 | self.assertRaises(oracledb.DatabaseError, self.cursor.execute, sql, 36 | rid=12345) 37 | self.assertRaises(oracledb.DatabaseError, self.cursor.execute, sql, 38 | rid="523lkhlf") 39 | 40 | def test_2903_insert_rowids(self): 41 | "2903 - test inserting rowids and verify they are inserted correctly" 42 | self.cursor.execute("select IntCol, rowid from TestNumbers") 43 | rows = self.cursor.fetchall() 44 | self.cursor.execute("truncate table TestRowids") 45 | self.cursor.executemany(""" 46 | insert into TestRowids 47 | (IntCol, RowidCol) 48 | values (:1, :2)""", rows) 49 | self.connection.commit() 50 | self.cursor.execute("select IntCol, RowidCol from TestRowids") 51 | rows = self.cursor.fetchall() 52 | sql = "select IntCol from TestNumbers where rowid = :val" 53 | for int_val, rowid in rows: 54 | self.cursor.execute(sql, val = rowid) 55 | rows = self.cursor.fetchall() 56 | self.assertEqual(len(rows), 1) 57 | self.assertEqual(rows[0][0], int_val) 58 | 59 | if __name__ == "__main__": 60 | test_env.run_test_cases() 61 | -------------------------------------------------------------------------------- /test/test_3000_subscription.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 3000 - Module for testing subscriptions 7 | """ 8 | 9 | import threading 10 | import unittest 11 | 12 | import cx_Oracle as oracledb 13 | import test_env 14 | 15 | class SubscriptionData: 16 | 17 | def __init__(self, num_messages_expected): 18 | self.condition = threading.Condition() 19 | self.num_messages_expected = num_messages_expected 20 | self.num_messages_received = 0 21 | 22 | def _process_message(self, message): 23 | pass 24 | 25 | def callback_handler(self, message): 26 | if message.type != oracledb.EVENT_DEREG: 27 | self._process_message(message) 28 | self.num_messages_received += 1 29 | if message.type == oracledb.EVENT_DEREG or \ 30 | self.num_messages_received == self.num_messages_expected: 31 | with self.condition: 32 | self.condition.notify() 33 | 34 | def wait_for_messages(self): 35 | if self.num_messages_received < self.num_messages_expected: 36 | with self.condition: 37 | self.condition.wait(10) 38 | 39 | 40 | class AQSubscriptionData(SubscriptionData): 41 | pass 42 | 43 | 44 | class DMLSubscriptionData(SubscriptionData): 45 | 46 | def __init__(self, num_messages_expected): 47 | super().__init__(num_messages_expected) 48 | self.table_operations = [] 49 | self.row_operations = [] 50 | self.rowids = [] 51 | 52 | def _process_message(self, message): 53 | table, = message.tables 54 | self.table_operations.append(table.operation) 55 | for row in table.rows: 56 | self.row_operations.append(row.operation) 57 | self.rowids.append(row.rowid) 58 | 59 | 60 | class TestCase(test_env.BaseTestCase): 61 | 62 | def test_3000_dml_subscription(self): 63 | "3000 - test subscription for insert, update, delete and truncate" 64 | 65 | # skip if running on the Oracle Cloud, which does not support 66 | # subscriptions currently 67 | if self.is_on_oracle_cloud(): 68 | message = "Oracle Cloud does not support subscriptions currently" 69 | self.skipTest(message) 70 | 71 | # truncate table in order to run test in known state 72 | self.cursor.execute("truncate table TestTempTable") 73 | 74 | # expected values 75 | table_operations = [ 76 | oracledb.OPCODE_INSERT, 77 | oracledb.OPCODE_UPDATE, 78 | oracledb.OPCODE_INSERT, 79 | oracledb.OPCODE_DELETE, 80 | oracledb.OPCODE_ALTER | oracledb.OPCODE_ALLROWS 81 | ] 82 | row_operations = [ 83 | oracledb.OPCODE_INSERT, 84 | oracledb.OPCODE_UPDATE, 85 | oracledb.OPCODE_INSERT, 86 | oracledb.OPCODE_DELETE 87 | ] 88 | rowids = [] 89 | 90 | # set up subscription 91 | data = DMLSubscriptionData(5) 92 | connection = test_env.get_connection(threaded=True, events=True) 93 | sub = connection.subscribe(callback=data.callback_handler, 94 | timeout=10, qos=oracledb.SUBSCR_QOS_ROWIDS) 95 | sub.registerquery("select * from TestTempTable") 96 | connection.autocommit = True 97 | cursor = connection.cursor() 98 | 99 | # insert statement 100 | cursor.execute(""" 101 | insert into TestTempTable (IntCol, StringCol) 102 | values (1, 'test')""") 103 | cursor.execute("select rowid from TestTempTable where IntCol = 1") 104 | rowids.extend(r for r, in cursor) 105 | 106 | # update statement 107 | cursor.execute(""" 108 | update TestTempTable set 109 | StringCol = 'update' 110 | where IntCol = 1""") 111 | cursor.execute("select rowid from TestTempTable where IntCol = 1") 112 | rowids.extend(r for r, in cursor) 113 | 114 | # second insert statement 115 | cursor.execute(""" 116 | insert into TestTempTable (IntCol, StringCol) 117 | values (2, 'test2')""") 118 | cursor.execute("select rowid from TestTempTable where IntCol = 2") 119 | rowids.extend(r for r, in cursor) 120 | 121 | # delete statement 122 | cursor.execute("delete TestTempTable where IntCol = 2") 123 | rowids.append(rowids[-1]) 124 | 125 | # truncate table 126 | cursor.execute("truncate table TestTempTable") 127 | 128 | # wait for all messages to be sent 129 | data.wait_for_messages() 130 | 131 | # verify the correct messages were sent 132 | self.assertEqual(data.table_operations, table_operations) 133 | self.assertEqual(data.row_operations, row_operations) 134 | self.assertEqual(data.rowids, rowids) 135 | 136 | # test string format of subscription object is as expected 137 | fmt = ">" 138 | expected = fmt % \ 139 | (test_env.get_main_user(), test_env.get_connect_string()) 140 | self.assertEqual(str(sub), expected) 141 | 142 | def test_3001_deprecations(self): 143 | "3001 - test to verify deprecations" 144 | connection = test_env.get_connection(threaded=True, events=True) 145 | self.assertRaises(oracledb.ProgrammingError, connection.subscribe, 146 | ip_address='www.oracle.in', 147 | ipAddress='www.oracle.in') 148 | self.assertRaises(oracledb.ProgrammingError, connection.subscribe, 149 | grouping_class=1, groupingClass=1) 150 | self.assertRaises(oracledb.ProgrammingError, connection.subscribe, 151 | grouping_value=3, groupingValue=3) 152 | self.assertRaises(oracledb.ProgrammingError, connection.subscribe, 153 | grouping_type=2, groupingType=2) 154 | self.assertRaises(oracledb.ProgrammingError, connection.subscribe, 155 | client_initiated=True, clientInitiated=True) 156 | 157 | @unittest.skip("multiple subscriptions cannot be created simultaneously") 158 | def test_3002_aq_subscription(self): 159 | "3002 - test subscription for AQ" 160 | 161 | # create queue and clear it of all messages 162 | queue = self.connection.queue("TEST_RAW_QUEUE") 163 | queue.deqoptions.wait = oracledb.DEQ_NO_WAIT 164 | while queue.deqone(): 165 | pass 166 | self.connection.commit() 167 | 168 | # set up subscription 169 | data = AQSubscriptionData(1) 170 | connection = test_env.get_connection(events=True) 171 | sub = connection.subscribe(namespace=oracledb.SUBSCR_NAMESPACE_AQ, 172 | name=queue.name, timeout=10, 173 | callback=data.callback_handler) 174 | 175 | # enqueue a message 176 | queue.enqone(self.connection.msgproperties(payload="Some data")) 177 | self.connection.commit() 178 | 179 | # wait for all messages to be sent 180 | data.wait_for_messages() 181 | 182 | if __name__ == "__main__": 183 | test_env.run_test_cases() 184 | -------------------------------------------------------------------------------- /test/test_3100_boolean_var.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 | # 4 | # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. 5 | # 6 | # Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, 7 | # Canada. All rights reserved. 8 | #------------------------------------------------------------------------------ 9 | 10 | """ 11 | 3100 - Module for testing boolean variables 12 | """ 13 | 14 | import unittest 15 | 16 | import cx_Oracle as oracledb 17 | import test_env 18 | 19 | @unittest.skipUnless(test_env.get_client_version() >= (12, 1), 20 | "unsupported client") 21 | class TestCase(test_env.BaseTestCase): 22 | 23 | def __test_bind_value_as_boolean(self, value): 24 | expected_result = str(bool(value)).upper() 25 | var = self.cursor.var(bool) 26 | var.setvalue(0, value) 27 | result = self.cursor.callfunc("pkg_TestBooleans.GetStringRep", str, 28 | (var,)) 29 | self.assertEqual(result, expected_result) 30 | 31 | def test_3100_bind_false(self): 32 | "3100 - test binding in a False value" 33 | result = self.cursor.callfunc("pkg_TestBooleans.GetStringRep", str, 34 | (False,)) 35 | self.assertEqual(result, "FALSE") 36 | 37 | def test_3101_bind_float_as_boolean(self): 38 | "3101 - test binding in a float as a boolean" 39 | self.__test_bind_value_as_boolean(0.0) 40 | self.__test_bind_value_as_boolean(1.0) 41 | 42 | def test_3102_bind_integer_as_boolean(self): 43 | "3102 - test binding in an integer as a boolean" 44 | self.__test_bind_value_as_boolean(0) 45 | self.__test_bind_value_as_boolean(1) 46 | 47 | def test_3103_bind_null(self): 48 | "3103 - test binding in a null value" 49 | self.cursor.setinputsizes(None, bool) 50 | result = self.cursor.callfunc("pkg_TestBooleans.GetStringRep", str, 51 | (None,)) 52 | self.assertEqual(result, "NULL") 53 | 54 | def test_3104_bind_out_false(self): 55 | "3104 - test binding out a boolean value (False)" 56 | result = self.cursor.callfunc("pkg_TestBooleans.IsLessThan10", 57 | oracledb.DB_TYPE_BOOLEAN, (15,)) 58 | self.assertEqual(result, False) 59 | 60 | def test_3105_bind_out_true(self): 61 | "3105 - test binding out a boolean value (True)" 62 | result = self.cursor.callfunc("pkg_TestBooleans.IsLessThan10", bool, 63 | (5,)) 64 | self.assertEqual(result, True) 65 | 66 | def test_3106_bind_string_as_boolean(self): 67 | "3106 - test binding in a string as a boolean" 68 | self.__test_bind_value_as_boolean("") 69 | self.__test_bind_value_as_boolean("0") 70 | 71 | def test_3107_bind_true(self): 72 | "3107 - test binding in a True value" 73 | result = self.cursor.callfunc("pkg_TestBooleans.GetStringRep", str, 74 | (True,)) 75 | self.assertEqual(result, "TRUE") 76 | 77 | if __name__ == "__main__": 78 | test_env.run_test_cases() 79 | -------------------------------------------------------------------------------- /test/test_3300_soda_database.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 3300 - Module for testing Simple Oracle Document Access (SODA) Database 7 | """ 8 | 9 | import json 10 | import unittest 11 | 12 | import cx_Oracle as oracledb 13 | import test_env 14 | 15 | @unittest.skipIf(test_env.skip_soda_tests(), 16 | "unsupported client/server combination") 17 | class TestCase(test_env.BaseTestCase): 18 | 19 | def __drop_existing_collections(self, soda_db): 20 | for name in soda_db.getCollectionNames(): 21 | soda_db.openCollection(name).drop() 22 | 23 | def __verify_doc(self, doc, raw_content, str_content=None, content=None, 24 | key=None, media_type='application/json'): 25 | self.assertEqual(doc.getContentAsBytes(), raw_content) 26 | if str_content is not None: 27 | self.assertEqual(doc.getContentAsString(), str_content) 28 | if content is not None: 29 | self.assertEqual(doc.getContent(), content) 30 | self.assertEqual(doc.key, key) 31 | self.assertEqual(doc.mediaType, media_type) 32 | 33 | def test_3300_create_document_with_json(self): 34 | "3300 - test creating documents with JSON data" 35 | soda_db = self.connection.getSodaDatabase() 36 | val = {"testKey1": "testValue1", "testKey2": "testValue2"} 37 | str_val = json.dumps(val) 38 | bytes_val = str_val.encode() 39 | key = "MyKey" 40 | media_type = "text/plain" 41 | doc = soda_db.createDocument(val) 42 | self.__verify_doc(doc, bytes_val, str_val, val) 43 | doc = soda_db.createDocument(str_val, key) 44 | self.__verify_doc(doc, bytes_val, str_val, val, key) 45 | doc = soda_db.createDocument(bytes_val, key, media_type) 46 | self.__verify_doc(doc, bytes_val, str_val, val, key, media_type) 47 | 48 | def test_3301_create_document_with_raw(self): 49 | "3301 - test creating documents with raw data" 50 | soda_db = self.connection.getSodaDatabase() 51 | val = b"" 52 | key = "MyRawKey" 53 | media_type = "text/html" 54 | doc = soda_db.createDocument(val) 55 | self.__verify_doc(doc, val) 56 | doc = soda_db.createDocument(val, key) 57 | self.__verify_doc(doc, val, key=key) 58 | doc = soda_db.createDocument(val, key, media_type) 59 | self.__verify_doc(doc, val, key=key, media_type=media_type) 60 | 61 | def test_3302_get_collection_names(self): 62 | "3302 - test getting collection names from the database" 63 | soda_db = self.connection.getSodaDatabase() 64 | self.__drop_existing_collections(soda_db) 65 | self.assertEqual(soda_db.getCollectionNames(), []) 66 | names = ["zCol", "dCol", "sCol", "aCol", "gCol"] 67 | sorted_names = list(sorted(names)) 68 | for name in names: 69 | soda_db.createCollection(name) 70 | self.assertEqual(soda_db.getCollectionNames(), sorted_names) 71 | self.assertEqual(soda_db.getCollectionNames(limit=2), sorted_names[:2]) 72 | self.assertEqual(soda_db.getCollectionNames("a"), sorted_names) 73 | self.assertEqual(soda_db.getCollectionNames("C"), sorted_names) 74 | self.assertEqual(soda_db.getCollectionNames("b", limit=3), 75 | sorted_names[1:4]) 76 | self.assertEqual(soda_db.getCollectionNames("z"), sorted_names[-1:]) 77 | 78 | def test_3303_open_collection(self): 79 | "3303 - test opening a collection" 80 | soda_db = self.connection.getSodaDatabase() 81 | self.__drop_existing_collections(soda_db) 82 | coll = soda_db.openCollection("CollectionThatDoesNotExist") 83 | self.assertEqual(coll, None) 84 | created_coll = soda_db.createCollection("TestOpenCollection") 85 | coll = soda_db.openCollection(created_coll.name) 86 | self.assertEqual(coll.name, created_coll.name) 87 | coll.drop() 88 | 89 | def test_3304_repr(self): 90 | "3304 - test SodaDatabase representation" 91 | con1 = self.connection 92 | con2 = test_env.get_connection() 93 | soda_db1 = self.connection.getSodaDatabase() 94 | soda_db2 = con1.getSodaDatabase() 95 | soda_db3 = con2.getSodaDatabase() 96 | self.assertEqual(str(soda_db1), str(soda_db2)) 97 | self.assertEqual(str(soda_db2), str(soda_db3)) 98 | 99 | def test_3305_negative(self): 100 | "3305 - test negative cases for SODA database methods" 101 | soda_db = self.connection.getSodaDatabase() 102 | self.assertRaises(TypeError, soda_db.createCollection) 103 | self.assertRaises(TypeError, soda_db.createCollection, 1) 104 | self.assertRaises(oracledb.DatabaseError, soda_db.createCollection, 105 | None) 106 | self.assertRaises(TypeError, soda_db.getCollectionNames, 1) 107 | 108 | if __name__ == "__main__": 109 | test_env.run_test_cases() 110 | -------------------------------------------------------------------------------- /test/test_3500_json.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 3500 - Module for testing the JSON data type. 7 | """ 8 | 9 | import datetime 10 | import decimal 11 | import unittest 12 | 13 | import cx_Oracle as oracledb 14 | import test_env 15 | 16 | @unittest.skipUnless(test_env.get_client_version() >= (21, 0), 17 | "unsupported client") 18 | @unittest.skipUnless(test_env.get_server_version() >= (21, 0), 19 | "unsupported server") 20 | class TestCase(test_env.BaseTestCase): 21 | 22 | json_data = [ 23 | True, 24 | False, 25 | 'String', 26 | b'Some Bytes', 27 | {}, 28 | {"name": None}, 29 | {"name": "John"}, 30 | {"age": 30}, 31 | {"Permanent": True}, 32 | { 33 | "employee": { 34 | "name":"John", 35 | "age": 30, 36 | "city": "Delhi", 37 | "Parmanent": True 38 | } 39 | }, 40 | { 41 | "employees": ["John", "Matthew", "James"] 42 | }, 43 | { 44 | "employees": [ 45 | { 46 | "employee1": {"name": "John", "city": "Delhi"} 47 | }, 48 | { 49 | "employee2": {"name": "Matthew", "city": "Mumbai"} 50 | }, 51 | { 52 | "employee3": {"name": "James", "city": "Bangalore"} 53 | } 54 | ] 55 | } 56 | ] 57 | 58 | def __bind_scalar_as_json(self, data): 59 | self.cursor.execute("truncate table TestJson") 60 | out_var = self.cursor.var(oracledb.DB_TYPE_JSON, 61 | arraysize=len(data)) 62 | self.cursor.setinputsizes(None, oracledb.DB_TYPE_JSON, out_var) 63 | bind_data = list(enumerate(data)) 64 | self.cursor.executemany(""" 65 | insert into TestJson values (:1, :2) 66 | returning JsonCol into :json_out""", bind_data) 67 | self.connection.commit() 68 | self.assertEqual(out_var.values, [[v] for v in data]) 69 | 70 | def test_3500_insert_and_fetch_single_json(self): 71 | "3500 - insert and fetch single row with JSON" 72 | self.cursor.execute("truncate table TestJson") 73 | self.cursor.setinputsizes(None, oracledb.DB_TYPE_JSON) 74 | self.cursor.execute("insert into TestJson values (:1, :2)", 75 | [1, self.json_data]) 76 | self.cursor.execute("select JsonCol from TestJson") 77 | result, = self.cursor.fetchone() 78 | self.assertEqual(result, self.json_data) 79 | 80 | def test_3501_execute_with_dml_returning(self): 81 | "3501 - inserting single rows with JSON and DML returning" 82 | json_val = self.json_data[11] 83 | self.cursor.execute("truncate table TestJson") 84 | json_out = self.cursor.var(oracledb.DB_TYPE_JSON) 85 | self.cursor.setinputsizes(None, oracledb.DB_TYPE_JSON, json_out) 86 | self.cursor.execute(""" 87 | insert into TestJson values (:1, :2) 88 | returning JsonCol into :json_out""", 89 | [1, json_val]) 90 | self.assertEqual(json_out.getvalue(0), [json_val]) 91 | 92 | def test_3502_insert_and_fetch_multiple_json(self): 93 | "3502 - insert and fetch multiple rows with JSON" 94 | self.cursor.execute("truncate table TestJson") 95 | self.cursor.setinputsizes(None, oracledb.DB_TYPE_JSON) 96 | data = list(enumerate(self.json_data)) 97 | self.cursor.executemany("insert into TestJson values(:1, :2)", data) 98 | self.cursor.execute("select * from TestJson") 99 | fetched_data = self.cursor.fetchall() 100 | self.assertEqual(fetched_data, data) 101 | 102 | def test_3503_executemany_with_dml_returning(self): 103 | "3503 - inserting multiple rows with JSON and DML returning" 104 | self.cursor.execute("truncate table TestJson") 105 | int_values = [i for i in range(len(self.json_data))] 106 | out_int_var = self.cursor.var(int, arraysize=len(int_values)) 107 | out_json_var = self.cursor.var(oracledb.DB_TYPE_JSON, 108 | arraysize=len(int_values)) 109 | self.cursor.setinputsizes(None, oracledb.DB_TYPE_JSON, out_int_var, 110 | out_json_var) 111 | data = list(zip(int_values, self.json_data)) 112 | self.cursor.executemany(""" 113 | insert into TestJson 114 | values(:int_val, :json_val) 115 | returning IntCol, JsonCol into :int_var, :json_var""", data) 116 | self.assertEqual(out_int_var.values, [[v] for v in int_values]) 117 | self.assertEqual(out_json_var.values, [[v] for v in self.json_data]) 118 | 119 | def test_3504_boolean(self): 120 | "3504 - test binding boolean values as scalar JSON values" 121 | data = [ 122 | True, 123 | False, 124 | True, 125 | True, 126 | False, 127 | True 128 | ] 129 | self.__bind_scalar_as_json(data) 130 | 131 | def test_3505_strings_and_bytes(self): 132 | "3505 - test binding strings/bytes values as scalar JSON values" 133 | data = [ 134 | "String 1", 135 | b"A raw value", 136 | "A much longer string", 137 | b"A much longer RAW value", 138 | "Short string", 139 | b"Y" 140 | ] 141 | self.__bind_scalar_as_json(data) 142 | 143 | def test_3506_datetime(self): 144 | "3506 - test binding dates/intervals as scalar JSON values" 145 | data = [ 146 | datetime.datetime.today(), 147 | datetime.datetime(2004, 2, 1, 3, 4, 5), 148 | datetime.datetime(2020, 12, 2, 13, 29, 14), 149 | datetime.timedelta(8.5), 150 | datetime.datetime(2002, 12, 13, 9, 36, 0), 151 | oracledb.Timestamp(2002, 12, 13, 9, 36, 0), 152 | datetime.datetime(2002, 12, 13) 153 | ] 154 | self.__bind_scalar_as_json(data) 155 | 156 | def test_3507_bind_number(self): 157 | "3507 - test binding number in json values" 158 | data = [ 159 | 0, 160 | 1, 161 | 25.25, 162 | 6088343244, 163 | -9999999999999999999, 164 | decimal.Decimal("0.25"), 165 | decimal.Decimal("10.25"), 166 | decimal.Decimal("319438950232418390.273596") 167 | ] 168 | self.__bind_scalar_as_json(data) 169 | 170 | if __name__ == "__main__": 171 | test_env.run_test_cases() 172 | -------------------------------------------------------------------------------- /test/test_3800_typehandler.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | #Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. 3 | #------------------------------------------------------------------------------ 4 | 5 | """ 6 | 3800 - Module for testing the input and output type handlers. 7 | """ 8 | 9 | import json 10 | 11 | import cx_Oracle as oracledb 12 | import test_env 13 | 14 | class Building(object): 15 | 16 | def __init__(self, building_id, description, num_floors): 17 | self.building_id = building_id 18 | self.description = description 19 | self.num_floors = num_floors 20 | 21 | def __repr__(self): 22 | return "" % (self.building_id, self.description) 23 | 24 | def __eq__(self, other): 25 | if isinstance(other, Building): 26 | return other.building_id == self.building_id \ 27 | and other.description == self.description \ 28 | and other.num_floors == self.num_floors 29 | return NotImplemented 30 | 31 | def to_json(self): 32 | return json.dumps(self.__dict__) 33 | 34 | @classmethod 35 | def from_json(cls, value): 36 | result = json.loads(value) 37 | return cls(**result) 38 | 39 | 40 | class TestCase(test_env.BaseTestCase): 41 | 42 | def building_in_converter(self, value): 43 | return value.to_json() 44 | 45 | def input_type_handler(self, cursor, value, num_elements): 46 | if isinstance(value, Building): 47 | return cursor.var(oracledb.STRING, arraysize=num_elements, 48 | inconverter=self.building_in_converter) 49 | 50 | def output_type_handler(self, cursor, name, default_type, size, precision, 51 | scale): 52 | if default_type == oracledb.STRING: 53 | return cursor.var(default_type, arraysize=cursor.arraysize, 54 | outconverter=Building.from_json) 55 | 56 | def test_3800(self): 57 | "3800 - binding unsupported python object without input type handler" 58 | self.cursor.execute("truncate table TestTempTable") 59 | sql = "insert into TestTempTable (IntCol, StringCol) values (:1, :2)" 60 | building = Building(1, "The First Building", 5) 61 | self.assertRaises(oracledb.NotSupportedError, self.cursor.execute, sql, 62 | (building.building_id, building)) 63 | 64 | def test_3801(self): 65 | "3801 - not callable input type handler" 66 | self.cursor.execute("truncate table TestTempTable") 67 | building = Building(1, "The First Building", 5) 68 | sql = "insert into TestTempTable (IntCol, StringCol) values (:1, :2)" 69 | self.cursor.inputtypehandler = 5 70 | self.assertRaises(TypeError, self.cursor.execute, sql, 71 | (building.building_id, building)) 72 | 73 | def test_3802(self): 74 | "3802 - binding unsupported python object with input type handler" 75 | self.cursor.execute("truncate table TestTempTable") 76 | building = Building(1, "The First Building", 5) 77 | sql = "insert into TestTempTable (IntCol, StringCol) values (:1, :2)" 78 | self.cursor.inputtypehandler = self.input_type_handler 79 | self.cursor.execute(sql, (building.building_id, building)) 80 | self.connection.commit() 81 | self.cursor.execute("select IntCol, StringCol from TestTempTable") 82 | self.assertEqual(self.cursor.fetchall(), 83 | [(building.building_id, building.to_json())]) 84 | 85 | def test_3803(self): 86 | "3803 - input type handler and output type handler on cursor level" 87 | self.cursor.execute("truncate table TestTempTable") 88 | building_one = Building(1, "The First Building", 5) 89 | building_two = Building(2, "The Second Building", 87) 90 | sql = "insert into TestTempTable (IntCol, StringCol) values (:1, :2)" 91 | cursor_one = self.connection.cursor() 92 | cursor_two = self.connection.cursor() 93 | cursor_one.inputtypehandler = self.input_type_handler 94 | cursor_one.execute(sql, (building_one.building_id, building_one)) 95 | self.connection.commit() 96 | cursor_one.execute("select IntCol, StringCol from TestTempTable") 97 | self.assertEqual(cursor_one.fetchall(), 98 | [(building_one.building_id, building_one.to_json())]) 99 | self.assertRaises(oracledb.NotSupportedError, cursor_two.execute, 100 | sql, (building_two.building_id, building_two)) 101 | cursor_two.outputtypehandler = self.output_type_handler 102 | cursor_two.execute("select IntCol, StringCol from TestTempTable") 103 | self.assertEqual(cursor_two.fetchall(), 104 | [(building_one.building_id, building_one)]) 105 | 106 | def test_3804(self): 107 | "3804 - input type handler and output type handler on connection level" 108 | self.cursor.execute("truncate table TestTempTable") 109 | building_one = Building(1, "The First Building", 5) 110 | building_two = Building(2, "The Second Building", 87) 111 | sql = "insert into TestTempTable (IntCol, StringCol) values (:1, :2)" 112 | connection = test_env.get_connection() 113 | connection.inputtypehandler = self.input_type_handler 114 | cursor_one = connection.cursor() 115 | cursor_two = connection.cursor() 116 | cursor_one.execute(sql, (building_one.building_id, building_one)) 117 | cursor_two.execute(sql, (building_two.building_id, building_two)) 118 | connection.commit() 119 | expected_data = [ 120 | (building_one.building_id, building_one), 121 | (building_two.building_id, building_two) 122 | ] 123 | connection.outputtypehandler = self.output_type_handler 124 | cursor_one.execute("select IntCol, StringCol from TestTempTable") 125 | self.assertEqual(cursor_one.fetchall(), expected_data) 126 | cursor_two.execute("select IntCol, StringCol from TestTempTable") 127 | self.assertEqual(cursor_two.fetchall(), expected_data) 128 | other_cursor = self.connection.cursor() 129 | self.assertRaises(oracledb.NotSupportedError, other_cursor.execute, 130 | sql, (building_one.building_id, building_one)) 131 | 132 | if __name__ == "__main__": 133 | test_env.run_test_cases() 134 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{36,37,38,39,310} 3 | 4 | [testenv] 5 | commands = {envpython} -m unittest discover -v -s test 6 | passenv = 7 | CX_ORACLE_TEST_MAIN_USER 8 | CX_ORACLE_TEST_MAIN_PASSWORD 9 | CX_ORACLE_TEST_PROXY_USER 10 | CX_ORACLE_TEST_PROXY_PASSWORD 11 | CX_ORACLE_TEST_CONNECT_STRING 12 | CX_ORACLE_TEST_ADMIN_USER 13 | CX_ORACLE_TEST_ADMIN_PASSWORD 14 | DPI_DEBUG_LEVEL 15 | ORACLE_HOME 16 | --------------------------------------------------------------------------------