Compare commits

...

12 Commits
main ... v1.1.x

Author SHA1 Message Date
Anthony Tuininga f2f2ed9fd5 Update documentation for querying corrupt data. 2022-09-28 17:21:32 -06:00
Anthony Tuininga 065d8ba81a Preparing to release python-oracledb 1.1.1. 2022-09-28 09:33:06 -06:00
Anthony Tuininga 790ee29d3c Reduce minimum version of cryptography package for ease of building on
Oracle Linux.
2022-09-28 09:32:43 -06:00
Anthony Tuininga 1674d8ce8f Fixed bug that caused "Connection.is_healthy()" to return "True" after a
connection has been killed.
2022-09-28 09:14:51 -06:00
Anthony Tuininga 6dda5b597c The mode of python-oracledb is now fixed only after a
call to "oracledb.init_oracle_client()", "oracledb.connect()" or
"oracledb.create_pool()" has completed successfully (#44).
2022-09-28 09:14:17 -06:00
Anthony Tuininga 0a59085f64 Additional test cases. 2022-09-28 09:13:57 -06:00
Anthony Tuininga 512826e028 Check for multiple probe/control/EOF packets being sent by the server. 2022-09-28 09:09:28 -06:00
Anthony Tuininga d3e14f6b87 Corrected and enhanced type checking issues (#52, #54, #60). 2022-09-28 09:08:54 -06:00
Anthony Tuininga aa9b1daa65 Fixed bug returning metadata of SODA documents inserted into a collection
using saveAndGet().
2022-09-28 09:08:30 -06:00
Anthony Tuininga b0f0c671b2 Fixed bug that prevented binding data of types DB_TYPE_ROWID and
DB_TYPE_UROWID.
2022-09-28 09:07:47 -06:00
Anthony Tuininga 6f37dec1d9 Documentation tweaks. 2022-09-28 09:07:10 -06:00
Anthony Tuininga 25fb52b6b2 Bump version to 1.1.1 in preparation for release of patch. 2022-09-28 09:06:36 -06:00
34 changed files with 569 additions and 279 deletions

View File

@ -1,4 +1,4 @@
# python-oracledb 1.0 # python-oracledb 1.1
python-oracledb is a [Python programming language][python] extension module python-oracledb is a [Python programming language][python] extension module
allowing Python programs to connect to [Oracle Database][oracledb]. It is the allowing Python programs to connect to [Oracle Database][oracledb]. It is the

View File

@ -2066,9 +2066,9 @@ DB API Types
.. data:: ROWID .. data:: ROWID
This type object is used to describe the pseudo column "rowid". The This type object is used to describe the pseudo column "rowid". The
database type :data:`DB_TYPE_ROWID` will compare equal to this value. If a database types :data:`DB_TYPE_ROWID` and :data:`DB_TYPE_UROWID` will
variable is created with this type, the database type compare equal to this value. If a variable is created with this type, the
:data:`DB_TYPE_VARCHAR` will be used. database type :data:`DB_TYPE_VARCHAR` will be used.
.. data:: STRING .. data:: STRING
@ -2281,6 +2281,17 @@ when binding data.
:data:`DATETIME`. :data:`DATETIME`.
.. data:: DB_TYPE_UROWID
Describes columns, attributes or array elements in a database that are of
type UROWID. It will compare equal to the DB API type :data:`ROWID`.
.. note::
This type is not supported in python-oracledb Thick mode.
See :ref:`querymetadatadiff`.
.. data:: DB_TYPE_VARCHAR .. data:: DB_TYPE_VARCHAR
Describes columns, attributes or array elements in a database that are of Describes columns, attributes or array elements in a database that are of

View File

@ -7,6 +7,40 @@ python-oracledb Release Notes
For deprecations, see :ref:`Deprecations <deprecations>`. For deprecations, see :ref:`Deprecations <deprecations>`.
oracledb 1.1.1 (September 2022)
-------------------------------
Thin Mode Changes
+++++++++++++++++
#) Fixed bug that prevented binding data of types
:data:`~oracledb.DB_TYPE_ROWID` and :data:`~oracledb.DB_TYPE_UROWID`.
#) Fixed bug that caused :meth:`Connection.is_healthy()` to return `True`
after a connection has been killed.
#) Internally, before a connection is returned from a pool, perform additional
checks in order to avoid returning a dead connection from the pool.
Thick Mode Changes
++++++++++++++++++
#) Fixed bug returning metadata of SODA documents inserted into a collection
using :meth:`SodaCollection.saveAndGet()`.
Common Changes
++++++++++++++
#) Fixed type checking errors
(`issue 52 <https://github.com/oracle/python-oracledb/issues/52>`__).
#) Enhanced type checking
(`issue 54 <https://github.com/oracle/python-oracledb/issues/54>`__),
(`issue 60 <https://github.com/oracle/python-oracledb/issues/60>`__).
#) The mode of python-oracledb is now fixed only after a call to
:meth:`oracledb.init_oracle_client()`, :meth:`oracledb.connect()` or
:meth:`oracledb.create_pool()` has completed successfully
(`issue 44 <https://github.com/oracle/python-oracledb/issues/44>`__).
#) Improved test suite and documentation.
oracledb 1.1.0 (September 2022) oracledb 1.1.0 (September 2022)
------------------------------- -------------------------------

View File

@ -463,11 +463,11 @@ values.
* - ROWID * - ROWID
- DB_TYPE_ROWID - DB_TYPE_ROWID
- Yes - Yes
- cannot be set - bytes, str
* - UROWID * - UROWID
- DB_TYPE_ROWID - DB_TYPE_ROWID, DB_TYPE_UROWID (only supported in python-oracledb Thin mode)
- Yes. May show DB_TYPE_UROWID in metadata. See :ref:`Query Metadata Differences <querymetadatadiff>`. - Yes. May show DB_TYPE_UROWID in metadata. See :ref:`Query Metadata Differences <querymetadatadiff>`.
- cannot be set - bytes, str
* - CHAR * - CHAR
- DB_TYPE_CHAR - DB_TYPE_CHAR
- Yes - Yes
@ -578,7 +578,7 @@ these arrays.
* - DB_TYPE_RAW * - DB_TYPE_RAW
- bytes, str - bytes, str
* - DB_TYPE_ROWID * - DB_TYPE_ROWID
- cannot be set - bytes, str
* - DB_TYPE_TIMESTAMP * - DB_TYPE_TIMESTAMP
- datetime.date, datetime.datetime - datetime.date, datetime.datetime
* - DB_TYPE_TIMESTAMP_LTZ * - DB_TYPE_TIMESTAMP_LTZ
@ -586,6 +586,6 @@ these arrays.
* - DB_TYPE_TIMESTAMP_TZ * - DB_TYPE_TIMESTAMP_TZ
- datetime.date, datetime.datetime - datetime.date, datetime.datetime
* - DB_TYPE_UROWID * - DB_TYPE_UROWID
- cannot be set - bytes, str
* - DB_TYPE_VARCHAR * - DB_TYPE_VARCHAR
- bytes, str - bytes, str

View File

@ -183,6 +183,48 @@ fetching a row and then updating that row by binding its rowid:
where rowid = :rid""", manager_id=205, rid=rowid) where rowid = :rid""", manager_id=205, rid=rowid)
Binding UROWID Values
=====================
Universal rowids (UROWID) are used to uniquely identify rows in index
organized tables. In python-oracledb, UROWID values are represented as strings.
The example below shows fetching a row from index organized table
``universal_rowids`` and then updating that row by binding its urowid:
.. code-block:: sql
CREATE TABLE universal_rowids (
int_col number(9) not null,
str_col varchar2(250) not null,
date_col date not null,
CONSTRAINT universal_rowids_pk PRIMARY KEY(int_col, str_col, date_col)
) ORGANIZATION INDEX
.. code-block:: python
ridvar = cursor.var(oracledb.DB_TYPE_UROWID)
# fetch the row
cursor.execute("""
begin
select rowid into :rid from universal_rowids
where int_col = 3;
end;""", rid=ridvar)
# update the row by binding UROWID
cursor.execute("""
update universal_rowids set
str_col = :str_val
where rowid = :rowid_val""",
str_val="String #33", rowid_val=ridvar)
Note that the type :attr:`oracledb.DB_TYPE_UROWID` is only supported in
python-oracledb Thin mode. For python-oracledb Thick mode, the database type
UROWID can be bound with type :attr:`oracledb.DB_TYPE_ROWID`.
See :ref:`querymetadatadiff`.
DML RETURNING Bind Variables DML RETURNING Bind Variables
============================ ============================

View File

@ -795,13 +795,13 @@ In order to install using the source on GitHub, use the following commands::
Note that if you download a source zip file directly from GitHub then you will Note that if you download a source zip file directly from GitHub then you will
also need to download an `ODPI-C <https://github.com/oracle/odpi>`__ source zip also need to download an `ODPI-C <https://github.com/oracle/odpi>`__ source zip
file and put the extracted contents inside the subdirectory file and put the extracted contents inside the "odpi" subdirectory, for example
"python-oracledb-*/src/oracledb/impl/thick/odpi". in "python-oracledb-main/src/oracledb/impl/thick/odpi".
Python-oracledb source code is also available from opensource.oracle.com. This Python-oracledb source code is also available from opensource.oracle.com. This
can be installed with:: can be installed with::
git clone --recurse-submodules git://opensource.oracle.com/git/oracle/python-oracledb.git git clone --recurse-submodules https://opensource.oracle.com/git/oracle/python-oracledb.git
cd python-oracledb cd python-oracledb
python setup.py build python setup.py build
python setup.py install python setup.py install

View File

@ -239,7 +239,7 @@ Python object that is returned by default. Python types can be changed with
- :attr:`oracledb.DB_TYPE_TIMESTAMP_TZ` - :attr:`oracledb.DB_TYPE_TIMESTAMP_TZ`
- datetime.datetime [2]_ - datetime.datetime [2]_
* - UROWID * - UROWID
- :attr:`oracledb.DB_TYPE_ROWID` - :attr:`oracledb.DB_TYPE_ROWID`, :attr:`oracledb.DB_TYPE_UROWID`
- str - str
* - VARCHAR2 * - VARCHAR2
- :attr:`oracledb.DB_TYPE_VARCHAR` - :attr:`oracledb.DB_TYPE_VARCHAR`
@ -744,15 +744,10 @@ Querying Corrupt Data
If queries fail with the error "codec can't decode byte" when you select data, If queries fail with the error "codec can't decode byte" when you select data,
then: then:
* Check your :ref:`character set <globalization>` is correct. Review the * Check if your :ref:`character set <globalization>` is correct. Review the
:ref:`database character sets <findingcharset>`. Check with :ref:`database character sets <findingcharset>`. Check with
:ref:`fetching-raw-data`. Consider using UTF-8, if this is appropriate: :ref:`fetching-raw-data`. Note that the encoding used for all character
data in python-oracledb is "UTF-8".
.. code-block:: python
connection = oracledb.connect(user="hr", password=userpwd,
dsn="dbhost.example.com/orclpdb",
encoding="UTF-8", nencoding="UTF-8")
* Check for corrupt data in the database. * Check for corrupt data in the database.

View File

@ -36,7 +36,7 @@ classifiers =
zip_safe = false zip_safe = false
python_requires = >=3.6 python_requires = >=3.6
setup_requires = cython setup_requires = cython
install_requires = cryptography>=3.4 install_requires = cryptography>=3.2.1
test_suite = tests test_suite = tests
packages = find: packages = find:
package_dir = package_dir =

View File

@ -32,7 +32,7 @@
import datetime import datetime
from . import connection as connection_module from . import connection as connection_module
from typing import Type, Union from typing import Union, List
from . import errors, exceptions from . import errors, exceptions
from .dbobject import DbObject, DbObjectType from .dbobject import DbObject, DbObjectType
@ -48,7 +48,7 @@ class Queue:
queue._impl = impl queue._impl = impl
return queue return queue
def _verify_message(self, message: Type["MessageProperties"]) -> None: def _verify_message(self, message: "MessageProperties") -> None:
""" """
Internal method used for verifying a message. Internal method used for verifying a message.
""" """
@ -58,7 +58,7 @@ class Queue:
errors._raise_err(errors.ERR_MESSAGE_HAS_NO_PAYLOAD) errors._raise_err(errors.ERR_MESSAGE_HAS_NO_PAYLOAD)
@property @property
def connection(self) -> Type["connection_module.Connection"]: def connection(self) -> "connection_module.Connection":
""" """
Returns the connection on which the queue was created. Returns the connection on which the queue was created.
""" """
@ -72,13 +72,13 @@ class Queue:
message_impls = self._impl.deq_many(max_num_messages) message_impls = self._impl.deq_many(max_num_messages)
return [MessageProperties._from_impl(impl) for impl in message_impls] return [MessageProperties._from_impl(impl) for impl in message_impls]
def deqMany(self, max_num_messages: int) -> list: def deqMany(self, max_num_messages: int) -> List["MessageProperties"]:
""" """
Deprecated: use deqmany() instead. Deprecated: use deqmany() instead.
""" """
return self.deqmany(max_num_messages) return self.deqmany(max_num_messages)
def deqone(self) -> Union[Type["MessageProperties"], None]: def deqone(self) -> Union["MessageProperties", None]:
""" """
Dequeues at most one message from the queue and returns it. If no Dequeues at most one message from the queue and returns it. If no
message is dequeued, None is returned. message is dequeued, None is returned.
@ -87,14 +87,14 @@ class Queue:
if message_impl is not None: if message_impl is not None:
return MessageProperties._from_impl(message_impl) return MessageProperties._from_impl(message_impl)
def deqOne(self) -> Union[Type["MessageProperties"], None]: def deqOne(self) -> Union["MessageProperties", None]:
""" """
Deprecated: use deqone() instead. Deprecated: use deqone() instead.
""" """
return self.deqone() return self.deqone()
@property @property
def deqoptions(self) -> Type["DeqOptions"]: def deqoptions(self) -> "DeqOptions":
""" """
Returns the options that will be used when dequeuing messages from the Returns the options that will be used when dequeuing messages from the
queue. queue.
@ -102,7 +102,7 @@ class Queue:
return self._deq_options return self._deq_options
@property @property
def deqOptions(self) -> Type["DeqOptions"]: def deqOptions(self) -> "DeqOptions":
""" """
Deprecated: use deqoptions instead. Deprecated: use deqoptions instead.
""" """
@ -131,7 +131,7 @@ class Queue:
""" """
return self.enqmany(messages) return self.enqmany(messages)
def enqone(self, message: Type["MessageProperties"]) -> None: def enqone(self, message: "MessageProperties") -> None:
""" """
Enqueues a single message into the queue. The message must be a message Enqueues a single message into the queue. The message must be a message
property object which has had its payload attribute set to a value that property object which has had its payload attribute set to a value that
@ -140,14 +140,14 @@ class Queue:
self._verify_message(message) self._verify_message(message)
self._impl.enq_one(message._impl) self._impl.enq_one(message._impl)
def enqOne(self, message: Type["MessageProperties"]) -> None: def enqOne(self, message: "MessageProperties") -> None:
""" """
Deprecated: use enqone() instead. Deprecated: use enqone() instead.
""" """
return self.enqone(message) return self.enqone(message)
@property @property
def enqoptions(self) -> Type["EnqOptions"]: def enqoptions(self) -> "EnqOptions":
""" """
Returns the options that will be used when enqueuing messages into the Returns the options that will be used when enqueuing messages into the
queue. queue.
@ -155,7 +155,7 @@ class Queue:
return self._enq_options return self._enq_options
@property @property
def enqOptions(self) -> Type["EnqOptions"]: def enqOptions(self) -> "EnqOptions":
""" """
Deprecated: use enqoptions() instead. Deprecated: use enqoptions() instead.
""" """

View File

@ -34,7 +34,7 @@
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
import functools import functools
from typing import Type, Union, Callable from typing import Union, Callable
import oracledb import oracledb
@ -575,7 +575,7 @@ class ConnectParams:
""" """
return self._impl.wallet_location return self._impl.wallet_location
def copy(self) -> Type["ConnectParams"]: def copy(self) -> "ConnectParams":
""" """
Creates a copy of the parameters and returns it. Creates a copy of the parameters and returns it.
""" """

View File

@ -64,7 +64,7 @@ class Connection:
def __init__(self, def __init__(self,
dsn: str=None, *, dsn: str=None, *,
pool: Type["pool_module.ConnectionPool"]=None, pool: "pool_module.ConnectionPool"=None,
params: ConnectParams=None, params: ConnectParams=None,
**kwargs) -> None: **kwargs) -> None:
""" """
@ -99,51 +99,52 @@ class Connection:
self._impl = None self._impl = None
# determine if thin mode is being used # determine if thin mode is being used
thin = driver_mode.check_and_return_mode() with driver_mode.get_manager() as mode_mgr:
thin = mode_mgr.thin
# determine which connection parameters to use # determine which connection parameters to use
if params is None: if params is None:
params_impl = base_impl.ConnectParamsImpl() params_impl = base_impl.ConnectParamsImpl()
elif not isinstance(params, ConnectParams): elif not isinstance(params, ConnectParams):
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS) errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
else:
params_impl = params._impl.copy()
if kwargs:
params_impl.set(kwargs)
if dsn is not None:
dsn = params_impl.parse_dsn(dsn, thin)
if dsn is None:
dsn = params_impl.get_connect_string()
# see if connection is being acquired from a pool
if pool is None:
pool_impl = None
elif not isinstance(pool, pool_module.ConnectionPool):
message = "pool must be an instance of oracledb.ConnectionPool"
raise TypeError(message)
else:
pool._verify_open()
pool_impl = pool._impl
# create thin or thick implementation object
if thin:
if pool is not None:
impl = pool_impl.acquire(params_impl)
else: else:
impl = thin_impl.ThinConnImpl(dsn, params_impl) params_impl = params._impl.copy()
impl.connect(params_impl) if kwargs:
else: params_impl.set(kwargs)
impl = thick_impl.ThickConnImpl(dsn, params_impl) if dsn is not None:
impl.connect(params_impl, pool_impl) dsn = params_impl.parse_dsn(dsn, thin)
self._impl = impl if dsn is None:
self._version = None dsn = params_impl.get_connect_string()
# invoke callback, if applicable # see if connection is being acquired from a pool
if impl.invoke_session_callback and pool is not None \ if pool is None:
and pool.session_callback is not None \ pool_impl = None
and callable(pool.session_callback): elif not isinstance(pool, pool_module.ConnectionPool):
pool.session_callback(self, params_impl.tag) message = "pool must be an instance of oracledb.ConnectionPool"
impl.invoke_session_callback = False raise TypeError(message)
else:
pool._verify_open()
pool_impl = pool._impl
# create thin or thick implementation object
if thin:
if pool is not None:
impl = pool_impl.acquire(params_impl)
else:
impl = thin_impl.ThinConnImpl(dsn, params_impl)
impl.connect(params_impl)
else:
impl = thick_impl.ThickConnImpl(dsn, params_impl)
impl.connect(params_impl, pool_impl)
self._impl = impl
self._version = None
# invoke callback, if applicable
if impl.invoke_session_callback and pool is not None \
and pool.session_callback is not None \
and callable(pool.session_callback):
pool.session_callback(self, params_impl.tag)
impl.invoke_session_callback = False
def __del__(self): def __del__(self):
if self._impl is not None: if self._impl is not None:
@ -502,7 +503,7 @@ class Connection:
This function performs a local check. To fully check a connection's This function performs a local check. To fully check a connection's
health, use ping() which performs a round-trip to the database. health, use ping() which performs a round-trip to the database.
""" """
return self._impl.get_is_healthy() return self._impl is not None and self._impl.get_is_healthy()
@property @property
def ltxid(self) -> bytes: def ltxid(self) -> bytes:
@ -603,7 +604,7 @@ class Connection:
""" """
return self.tpc_prepare() return self.tpc_prepare()
def queue(self, name: str, payload_type: [DbObjectType, str]=None, *, def queue(self, name: str, payload_type: Union[DbObjectType, str]=None, *,
payloadType: DbObjectType=None) -> Queue: payloadType: DbObjectType=None) -> Queue:
""" """
Creates and returns a queue which is used to enqueue and dequeue Creates and returns a queue which is used to enqueue and dequeue
@ -1001,7 +1002,7 @@ def _connection_factory(f):
""" """
@functools.wraps(f) @functools.wraps(f)
def connect(dsn: str=None, *, def connect(dsn: str=None, *,
pool: Type["pool_module.ConnectionPool"]=None, pool: "pool_module.ConnectionPool"=None,
conn_class: Type[Connection]=Connection, conn_class: Type[Connection]=Connection,
params: ConnectParams=None, params: ConnectParams=None,
**kwargs) -> Connection: **kwargs) -> Connection:
@ -1014,7 +1015,7 @@ def _connection_factory(f):
@_connection_factory @_connection_factory
def connect(dsn: str=None, *, def connect(dsn: str=None, *,
pool: Type["pool_module.ConnectionPool"]=None, pool: "pool_module.ConnectionPool"=None,
conn_class: Type[Connection]=Connection, conn_class: Type[Connection]=Connection,
params: ConnectParams=None, params: ConnectParams=None,
user: str=None, user: str=None,

View File

@ -29,7 +29,7 @@
# fetching results from queries. # fetching results from queries.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
from typing import Any, Type, Union, Callable from typing import Any, Union, Callable
from . import __name__ as MODULE_NAME from . import __name__ as MODULE_NAME
from . import errors, exceptions from . import errors, exceptions
@ -42,7 +42,7 @@ from .dbobject import DbObjectType
class Cursor: class Cursor:
__module__ = MODULE_NAME __module__ = MODULE_NAME
def __init__(self, connection: Type["connection_module.Connection"], def __init__(self, connection: "connection_module.Connection",
scrollable: bool = False) -> None: scrollable: bool = False) -> None:
self._impl = None self._impl = None
self.connection = connection self.connection = connection
@ -309,7 +309,7 @@ class Cursor:
def execute(self, statement: Union[str, None], def execute(self, statement: Union[str, None],
parameters: Union[list, tuple, dict]=None, parameters: Union[list, tuple, dict]=None,
**keyword_parameters: dict) -> Union[Type["Cursor"], None]: **keyword_parameters: dict) -> Union["Cursor", None]:
""" """
Execute a statement against the database. Execute a statement against the database.
@ -739,7 +739,7 @@ class Cursor:
encoding_errors: str=None, encoding_errors: str=None,
bypass_decode: bool=False, bypass_decode: bool=False,
*, *,
encodingErrors: str=None) -> Type["Var"]: encodingErrors: str=None) -> "Var":
""" """
Create a variable with the specified characteristics. This method was Create a variable with the specified characteristics. This method was
designed for use with PL/SQL in/out variables where the length or type designed for use with PL/SQL in/out variables where the length or type

View File

@ -29,7 +29,7 @@
# object type metadata: DbObject, DbObjectType and DbObjectAttr. # object type metadata: DbObject, DbObjectType and DbObjectAttr.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
from typing import Sequence, Type, Union from typing import Sequence, Union
from . import __name__ as MODULE_NAME from . import __name__ as MODULE_NAME
from .base_impl import DbType from .base_impl import DbType
@ -91,7 +91,7 @@ class DbObject:
ix = self._impl.get_next_index(ix) ix = self._impl.get_next_index(ix)
return result return result
def copy(self) -> Type["DbObject"]: def copy(self) -> "DbObject":
""" """
Create a copy of the object and return it. Create a copy of the object and return it.
""" """
@ -181,7 +181,7 @@ class DbObject:
self._impl.trim(num) self._impl.trim(num)
@property @property
def type(self) -> Type["DbObjectType"]: def type(self) -> "DbObjectType":
""" """
Returns an ObjectType corresponding to the type of the object. Returns an ObjectType corresponding to the type of the object.
""" """
@ -211,7 +211,7 @@ class DbObjectAttr:
return self._impl.name return self._impl.name
@property @property
def type(self) -> Union[Type["DbObjectType"], DbType]: def type(self) -> Union["DbObjectType", DbType]:
""" """
This read-only attribute returns the type of the attribute. This will This read-only attribute returns the type of the attribute. This will
be an Oracle Object Type if the variable binds Oracle objects; be an Oracle Object Type if the variable binds Oracle objects;
@ -274,7 +274,7 @@ class DbObjectType:
return self._impl.name return self._impl.name
@property @property
def element_type(self) -> Union[Type["DbObjectType"], DbType]: def element_type(self) -> Union["DbObjectType", DbType]:
""" """
This read-only attribute returns the type of elements found in This read-only attribute returns the type of elements found in
collections of this type, if iscollection is True; otherwise, it collections of this type, if iscollection is True; otherwise, it

View File

@ -31,27 +31,63 @@
# simultaneously. # simultaneously.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
import threading
from . import errors from . import errors
# this flag is used to indicate which mode is currently being used: # The DriverModeHandler class is used to manage which mode the driver is using.
# None: neither thick nor thin implementation has been used yet #
# False: thick implementation is being used # The "thin_mode" flag contains the current state:
# True: thin implementation is being used # None: neither thick nor thin implementation has been used yet
thin_mode = None # False: thick implementation is being used
# True: thin implementation is being used
def check_and_return_mode(requested_thin_mode=None): #
# The "requested_thin_mode" flag is set to the mode that is being requested:
# False: thick implementation is being initialized
# True: thin implementation is being initialized
class DriverModeManager:
""" """
Internal function to return the current mode of python-oracledb. Manages the mode the driver is using. The "thin_mode" flag contains the
current state:
None: neither thick nor thin implementation has been used yet
False: thick implementation is being used
True: thin implementation is being used
The "requested_thin_mode" is set to the mode that is being requested, but
only while initialization is taking place (otherwise, it contains the value
None):
False: thick implementation is being initialized
True: thin implementation is being initialized
The condition is used to ensure that only one thread is performing
initialization.
"""
def __init__(self):
self.thin_mode = None
self.requested_thin_mode = None
self.condition = threading.Condition()
If neither the thick nor the thin implementation have been used yet (the def __enter__(self):
value of thin_mode is None), then: return self
- the mode is set to the requested mode, or def __exit__(self, exc_type, exc_value, exc_tb):
with self.condition:
if exc_type is None and exc_value is None and exc_tb is None \
and self.requested_thin_mode is not None:
self.thin_mode = self.requested_thin_mode
self.requested_thin_mode = None
self.condition.notify()
- the mode is set to thin, if no mode is requested. @property
def thin(self):
if self.requested_thin_mode is not None:
return self.requested_thin_mode
return self.thin_mode
Otherwise, if requested_thin_mode is used and the mode requested manager = DriverModeManager()
does not match the current mode, an error is raised.
def get_manager(requested_thin_mode=None):
"""
Returns the manager, but only after ensuring that no other threads are
attempting to initialize the mode.
NOTE: the current implementation of the driver only requires NOTE: the current implementation of the driver only requires
requested_thin_mode to be set when initializing the thick mode; for this requested_thin_mode to be set when initializing the thick mode; for this
@ -59,15 +95,19 @@ def check_and_return_mode(requested_thin_mode=None):
being created. If this assumption changes, a new error message will be being created. If this assumption changes, a new error message will be
required. required.
""" """
global thin_mode with manager.condition:
if thin_mode is None: if manager.thin_mode is None:
if requested_thin_mode is None: if manager.requested_thin_mode is not None:
thin_mode = True manager.condition.wait()
else: if manager.thin_mode is None:
thin_mode = requested_thin_mode if requested_thin_mode is None:
elif requested_thin_mode is not None and requested_thin_mode != thin_mode: manager.requested_thin_mode = True
errors._raise_err(errors.ERR_THIN_CONNECTION_ALREADY_CREATED) else:
return thin_mode manager.requested_thin_mode = requested_thin_mode
elif requested_thin_mode is not None \
and requested_thin_mode != manager.thin_mode:
errors._raise_err(errors.ERR_THIN_CONNECTION_ALREADY_CREATED)
return manager
def is_thin_mode() -> bool: def is_thin_mode() -> bool:
@ -77,14 +117,14 @@ def is_thin_mode() -> bool:
Immediately after python-oracledb is imported, this function will return Immediately after python-oracledb is imported, this function will return
True indicating that python-oracledb defaults to Thin mode. If True indicating that python-oracledb defaults to Thin mode. If
oracledb.init_oracle_client() is called, then a subsequent call to oracledb.init_oracle_client() is called successfully, then a subsequent
is_thin_mode() will return False indicating that Thick mode is enabled. call to is_thin_mode() will return False indicating that Thick mode is
Once the first standalone connection or connection pool is created, or a enabled. Once the first standalone connection or connection pool is
call to oracledb.init_oracle_client() is made, then python-oracledb's mode created succesfully, or a call to oracledb.init_oracle_client() is made
is fixed and the value returned by is_thin_mode() will never change for the successfully, then python-oracledb's mode is fixed and the value returned
lifetime of the process. by is_thin_mode() will never change for the lifetime of the process.
""" """
if thin_mode is not None: if manager.thin_mode is not None:
return thin_mode return manager.thin_mode
return True return True

View File

@ -452,31 +452,32 @@ def init_oracle_client(lib_dir=None, config_dir=None, error_url=None,
if params_tuple != driver_context_params: if params_tuple != driver_context_params:
errors._raise_err(errors.ERR_LIBRARY_ALREADY_INITIALIZED) errors._raise_err(errors.ERR_LIBRARY_ALREADY_INITIALIZED)
return return
driver_mode.check_and_return_mode(requested_thin_mode=False) with driver_mode.get_manager(requested_thin_mode=False) as mode_mgr:
memset(&params, 0, sizeof(dpiContextCreateParams)) memset(&params, 0, sizeof(dpiContextCreateParams))
encoding_bytes = constants.ENCODING.encode() encoding_bytes = constants.ENCODING.encode()
params.defaultEncoding = encoding_bytes params.defaultEncoding = encoding_bytes
if config_dir is None: if config_dir is None:
config_dir = defaults.config_dir config_dir = defaults.config_dir
if lib_dir is not None: if lib_dir is not None:
lib_dir_bytes = lib_dir.encode() lib_dir_bytes = lib_dir.encode()
params.oracleClientLibDir = lib_dir_bytes params.oracleClientLibDir = lib_dir_bytes
if config_dir is not None: if config_dir is not None:
config_dir_bytes = config_dir.encode() config_dir_bytes = config_dir.encode()
params.oracleClientConfigDir = config_dir_bytes params.oracleClientConfigDir = config_dir_bytes
if driver_name is None: if driver_name is None:
driver_name = f"{constants.DRIVER_NAME} thk : {VERSION}" driver_name = f"{constants.DRIVER_NAME} thk : {VERSION}"
driver_name_bytes = driver_name.encode() driver_name_bytes = driver_name.encode()
params.defaultDriverName = driver_name_bytes params.defaultDriverName = driver_name_bytes
if error_url is not None: if error_url is not None:
error_url_bytes = error_url.encode() error_url_bytes = error_url.encode()
else: else:
error_url_bytes = constants.INSTALLATION_URL.encode() error_url_bytes = constants.INSTALLATION_URL.encode()
params.loadErrorUrl = error_url_bytes params.loadErrorUrl = error_url_bytes
if dpiContext_createWithParams(DPI_MAJOR_VERSION, DPI_MINOR_VERSION, if dpiContext_createWithParams(DPI_MAJOR_VERSION, DPI_MINOR_VERSION,
&params, &driver_context, &error_info) < 0: &params, &driver_context,
_raise_from_info(&error_info) &error_info) < 0:
driver_context_params = params_tuple _raise_from_info(&error_info)
driver_context_params = params_tuple
def init_thick_impl(package): def init_thick_impl(package):

View File

@ -1016,15 +1016,22 @@ cdef class ReadBuffer:
const char_type *input_ptr const char_type *input_ptr
bytearray output_value bytearray output_value
uint32_t num_bytes uint32_t num_bytes
uint8_t length
Rowid rowid Rowid rowid
# check for null # check for null
self.read_ub4(&num_bytes) self.read_ub1(&length)
if num_bytes == 0: if _is_null_length(length):
return None return None
# handle physical rowid if length == 1:
self.skip_ub1()
else:
self.read_ub4(&num_bytes)
self.read_raw_bytes_chunked(&input_ptr, &input_len) self.read_raw_bytes_chunked(&input_ptr, &input_len)
# handle physical rowid
if input_ptr[0] == 1: if input_ptr[0] == 1:
rowid.rba = unpack_uint32(&input_ptr[1], BYTE_ORDER_MSB) rowid.rba = unpack_uint32(&input_ptr[1], BYTE_ORDER_MSB)
rowid.partition_id = unpack_uint16(&input_ptr[5], BYTE_ORDER_MSB) rowid.partition_id = unpack_uint16(&input_ptr[5], BYTE_ORDER_MSB)

View File

@ -325,7 +325,8 @@ cdef class ThinConnImpl(BaseConnImpl):
return self._internal_name return self._internal_name
def get_is_healthy(self): def get_is_healthy(self):
return not self._protocol._read_buf._session_needs_to_be_closed return self._protocol._socket is not None \
and not self._protocol._read_buf._session_needs_to_be_closed
def get_ltxid(self): def get_ltxid(self):
return self._ltxid or b'' return self._ltxid or b''

View File

@ -621,6 +621,7 @@ DEF TNS_MAX_CURSORS_TO_CLOSE = 500
DEF TNS_TXN_IN_PROGRESS = 0x00000002 DEF TNS_TXN_IN_PROGRESS = 0x00000002
DEF TNS_MAX_CONNECT_DATA = 230 DEF TNS_MAX_CONNECT_DATA = 230
DEF TNS_CHUNK_SIZE = 32767 DEF TNS_CHUNK_SIZE = 32767
DEF TNS_MAX_UROWID_LENGTH = 3950
# base 64 encoding alphabet # base 64 encoding alphabet
DEF TNS_BASE64_ALPHABET = \ DEF TNS_BASE64_ALPHABET = \

View File

@ -537,7 +537,8 @@ cdef class MessageWithData(Message):
if var_impl.bypass_decode: if var_impl.bypass_decode:
ora_type_num = TNS_DATA_TYPE_RAW ora_type_num = TNS_DATA_TYPE_RAW
if buffer_size == 0 and ora_type_num != TNS_DATA_TYPE_LONG \ if buffer_size == 0 and ora_type_num != TNS_DATA_TYPE_LONG \
and ora_type_num != TNS_DATA_TYPE_LONG_RAW: and ora_type_num != TNS_DATA_TYPE_LONG_RAW \
and ora_type_num != TNS_DATA_TYPE_UROWID:
column_value = None # column is null by describe column_value = None # column is null by describe
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \ elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
or ora_type_num == TNS_DATA_TYPE_CHAR \ or ora_type_num == TNS_DATA_TYPE_CHAR \
@ -557,7 +558,7 @@ cdef class MessageWithData(Message):
column_value = buf.read_date() column_value = buf.read_date()
elif ora_type_num == TNS_DATA_TYPE_ROWID: elif ora_type_num == TNS_DATA_TYPE_ROWID:
if not self.in_fetch: if not self.in_fetch:
column_value = buf.read_urowid() column_value = buf.read_str(TNS_CS_IMPLICIT)
else: else:
buf.read_ub1(&num_bytes) buf.read_ub1(&num_bytes)
if _is_null_length(num_bytes): if _is_null_length(num_bytes):
@ -566,7 +567,10 @@ cdef class MessageWithData(Message):
buf.read_rowid(&rowid) buf.read_rowid(&rowid)
column_value = _encode_rowid(&rowid) column_value = _encode_rowid(&rowid)
elif ora_type_num == TNS_DATA_TYPE_UROWID: elif ora_type_num == TNS_DATA_TYPE_UROWID:
column_value = buf.read_urowid() if not self.in_fetch:
column_value = buf.read_str(TNS_CS_IMPLICIT)
else:
column_value = buf.read_urowid()
elif ora_type_num == TNS_DATA_TYPE_BINARY_DOUBLE: elif ora_type_num == TNS_DATA_TYPE_BINARY_DOUBLE:
column_value = buf.read_binary_double() column_value = buf.read_binary_double()
elif ora_type_num == TNS_DATA_TYPE_BINARY_FLOAT: elif ora_type_num == TNS_DATA_TYPE_BINARY_FLOAT:
@ -895,11 +899,14 @@ cdef class MessageWithData(Message):
list bind_var_impls) except -1: list bind_var_impls) except -1:
cdef: cdef:
uint8_t ora_type_num, flag uint8_t ora_type_num, flag
uint32_t buffer_size
ThinVarImpl var_impl ThinVarImpl var_impl
for var_impl in bind_var_impls: for var_impl in bind_var_impls:
ora_type_num = var_impl.dbtype._ora_type_num ora_type_num = var_impl.dbtype._ora_type_num
if ora_type_num == TNS_DATA_TYPE_ROWID: buffer_size = var_impl.buffer_size
ora_type_num = TNS_DATA_TYPE_UROWID if ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
ora_type_num = TNS_DATA_TYPE_VARCHAR
buffer_size = TNS_MAX_UROWID_LENGTH
flag = TNS_BIND_USE_INDICATORS flag = TNS_BIND_USE_INDICATORS
if var_impl.is_array: if var_impl.is_array:
flag |= TNS_BIND_ARRAY flag |= TNS_BIND_ARRAY
@ -909,10 +916,10 @@ cdef class MessageWithData(Message):
# expects that and complains if any other value is sent! # expects that and complains if any other value is sent!
buf.write_uint8(0) buf.write_uint8(0)
buf.write_uint8(0) buf.write_uint8(0)
if var_impl.buffer_size >= TNS_MIN_LONG_LENGTH: if buffer_size >= TNS_MIN_LONG_LENGTH:
buf.write_ub4(TNS_MAX_LONG_LENGTH) buf.write_ub4(TNS_MAX_LONG_LENGTH)
else: else:
buf.write_ub4(var_impl.buffer_size) buf.write_ub4(buffer_size)
if var_impl.is_array: if var_impl.is_array:
buf.write_ub4(var_impl.num_elements) buf.write_ub4(var_impl.num_elements)
else: else:
@ -996,6 +1003,9 @@ cdef class MessageWithData(Message):
num_bytes = <uint32_t> len(lob_impl._locator) num_bytes = <uint32_t> len(lob_impl._locator)
buf.write_ub4(num_bytes) buf.write_ub4(num_bytes)
buf.write_bytes_chunked(lob_impl._locator) buf.write_bytes_chunked(lob_impl._locator)
elif ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
temp_bytes = (<str> value).encode()
buf.write_bytes_chunked(temp_bytes)
else: else:
errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED, errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED,
name=var_impl.dbtype.name) name=var_impl.dbtype.name)

View File

@ -331,8 +331,10 @@ cdef class ThinPoolImpl(BasePoolImpl):
read_buf = conn_impl._protocol._read_buf read_buf = conn_impl._protocol._read_buf
if not read_buf._session_needs_to_be_closed: if not read_buf._session_needs_to_be_closed:
socket_list = [conn_impl._protocol._socket] socket_list = [conn_impl._protocol._socket]
read_socks, _, _ = select.select(socket_list, [], [], 0) while not read_buf._session_needs_to_be_closed:
if read_socks: read_socks, _, _ = select.select(socket_list, [], [], 0)
if not read_socks:
break
read_buf.check_control_packet() read_buf.check_control_packet()
if read_buf._session_needs_to_be_closed: if read_buf._session_needs_to_be_closed:
with self._condition: with self._condition:

View File

@ -88,17 +88,18 @@ class ConnectionPool:
params_impl.set(kwargs) params_impl.set(kwargs)
self._connection_type = \ self._connection_type = \
params_impl.connectiontype or connection_module.Connection params_impl.connectiontype or connection_module.Connection
thin = driver_mode.check_and_return_mode() with driver_mode.get_manager() as mode_mgr:
if dsn is not None: thin = mode_mgr.thin
dsn = params_impl.parse_dsn(dsn, thin) if dsn is not None:
if dsn is None: dsn = params_impl.parse_dsn(dsn, thin)
dsn = params_impl.get_connect_string() if dsn is None:
if thin: dsn = params_impl.get_connect_string()
impl = thin_impl.ThinPoolImpl(dsn, params_impl) if thin:
else: impl = thin_impl.ThinPoolImpl(dsn, params_impl)
impl = thick_impl.ThickPoolImpl(dsn, params_impl) else:
self._impl = impl impl = thick_impl.ThickPoolImpl(dsn, params_impl)
self.session_callback = params_impl.session_callback self._impl = impl
self.session_callback = params_impl.session_callback
def __del__(self): def __del__(self):
if self._impl is not None: if self._impl is not None:
@ -120,7 +121,7 @@ class ConnectionPool:
tag: str=None, tag: str=None,
matchanytag: bool=False, matchanytag: bool=False,
shardingkey: list=None, shardingkey: list=None,
supershardingkey: list=None) -> Type["connection_module.Connection"]: supershardingkey: list=None) -> "connection_module.Connection":
""" """
Acquire a connection from the pool and return it. Acquire a connection from the pool and return it.
@ -177,7 +178,7 @@ class ConnectionPool:
self._impl.close(force) self._impl.close(force)
self._impl = None self._impl = None
def drop(self, connection: Type["connection_module.Connection"]) -> None: def drop(self, connection: "connection_module.Connection") -> None:
""" """
Drop the connection from the pool, which is useful if the connection is Drop the connection from the pool, which is useful if the connection is
no longer usable (such as when the database session is killed). no longer usable (such as when the database session is killed).
@ -322,7 +323,7 @@ class ConnectionPool:
def ping_interval(self, value: int) -> None: def ping_interval(self, value: int) -> None:
self._impl.set_ping_interval(value) self._impl.set_ping_interval(value)
def release(self, connection: Type["connection_module.Connection"], def release(self, connection: "connection_module.Connection",
tag: str=None) -> None: tag: str=None) -> None:
""" """
Release the connection back to the pool now, rather than whenever Release the connection back to the pool now, rather than whenever

View File

@ -468,7 +468,7 @@ class PoolParams(ConnectParams):
""" """
return self._impl.wait_timeout return self._impl.wait_timeout
def copy(self) -> Type["PoolParams"]: def copy(self) -> "PoolParams":
""" """
Creates a copy of the parameters and returns it. Creates a copy of the parameters and returns it.
""" """

View File

@ -29,7 +29,7 @@
# SodaDatabase, SodaCollection, SodaDocument, SodaDocCursor and SodaOperation. # SodaDatabase, SodaCollection, SodaDocument, SodaDocCursor and SodaOperation.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
from typing import Type, Union from typing import Union, List
import json import json
from . import connection from . import connection
@ -58,7 +58,7 @@ class SodaDatabase:
return json.dumps(content).encode() return json.dumps(content).encode()
def createCollection(self, name: str, metadata: Union[str, dict]=None, def createCollection(self, name: str, metadata: Union[str, dict]=None,
mapMode: bool=False) -> Type["SodaCollection"]: mapMode: bool=False) -> "SodaCollection":
""" """
Creates a SODA collection with the given name and returns a new SODA Creates a SODA collection with the given name and returns a new SODA
collection object. If you try to create a collection, and a collection collection object. If you try to create a collection, and a collection
@ -85,7 +85,7 @@ class SodaDatabase:
return SodaCollection._from_impl(self, collection_impl) return SodaCollection._from_impl(self, collection_impl)
def createDocument(self, content: object, key: str=None, def createDocument(self, content: object, key: str=None,
mediaType: str="application/json") -> Type["SodaDocument"]: mediaType: str="application/json") -> "SodaDocument":
""" """
Creates a SODA document usable for SODA write operations. You only need Creates a SODA document usable for SODA write operations. You only need
to use this method if your collection requires client-assigned keys or to use this method if your collection requires client-assigned keys or
@ -112,7 +112,8 @@ class SodaDatabase:
doc_impl = self._impl.create_document(content_bytes, key, mediaType) doc_impl = self._impl.create_document(content_bytes, key, mediaType)
return SodaDocument._from_impl(doc_impl) return SodaDocument._from_impl(doc_impl)
def getCollectionNames(self, startName: str=None, limit: int=0) -> list: def getCollectionNames(self, startName: str=None,
limit: int=0) -> List[str]:
""" """
Returns a list of the names of collections in the database that match Returns a list of the names of collections in the database that match
the criteria, in alphabetical order. the criteria, in alphabetical order.
@ -126,7 +127,7 @@ class SodaDatabase:
""" """
return self._impl.get_collection_names(startName, limit) return self._impl.get_collection_names(startName, limit)
def openCollection(self, name: str) -> Type["SodaCollection"]: def openCollection(self, name: str) -> "SodaCollection":
""" """
Opens an existing collection with the given name and returns a new SODA Opens an existing collection with the given name and returns a new SODA
collection object. If a collection with that name does not exist, None collection object. If a collection with that name does not exist, None
@ -191,7 +192,7 @@ class SodaCollection:
""" """
return self._impl.drop_index(name, force) return self._impl.drop_index(name, force)
def find(self) -> Type["SodaOperation"]: def find(self) -> "SodaOperation":
""" """
This method is used to begin an operation that will act upon documents This method is used to begin an operation that will act upon documents
in the collection. It creates and returns a SodaOperation object which in the collection. It creates and returns a SodaOperation object which
@ -200,7 +201,7 @@ class SodaCollection:
""" """
return SodaOperation(self) return SodaOperation(self)
def getDataGuide(self) -> Type["SodaDocument"]: def getDataGuide(self) -> "SodaDocument":
""" """
Returns a SODA document object containing property names, data types Returns a SODA document object containing property names, data types
and lengths inferred from the JSON documents in the collection. It can and lengths inferred from the JSON documents in the collection. It can
@ -253,7 +254,7 @@ class SodaCollection:
self._impl.insert_one(doc_impl, hint=None, return_doc=False) self._impl.insert_one(doc_impl, hint=None, return_doc=False)
def insertOneAndGet(self, doc: object, def insertOneAndGet(self, doc: object,
hint: str=None) -> Type["SodaDocument"]: hint: str=None) -> "SodaDocument":
""" """
Similarly to insertOne() this method inserts a given document into the Similarly to insertOne() this method inserts a given document into the
collection. The only difference is that it returns a SODA Document collection. The only difference is that it returns a SODA Document
@ -300,7 +301,7 @@ class SodaCollection:
doc_impl = self._process_doc_arg(doc) doc_impl = self._process_doc_arg(doc)
self._impl.save(doc_impl, hint=None, return_doc=False) self._impl.save(doc_impl, hint=None, return_doc=False)
def saveAndGet(self, doc: object, hint: str=None) -> Type["SodaDocument"]: def saveAndGet(self, doc: object, hint: str=None) -> "SodaDocument":
""" """
Saves a document into the collection. This method is equivalent to Saves a document into the collection. This method is equivalent to
insertOneAndGet() except that if client-assigned keys are used, and the insertOneAndGet() except that if client-assigned keys are used, and the
@ -318,7 +319,7 @@ class SodaCollection:
doc_impl = self._process_doc_arg(doc) doc_impl = self._process_doc_arg(doc)
if hint is not None and not isinstance(hint, str): if hint is not None and not isinstance(hint, str):
raise TypeError("expecting a string") raise TypeError("expecting a string")
return_doc_impl = self._impl.save(doc_impl, hint, return_doc=False) return_doc_impl = self._impl.save(doc_impl, hint, return_doc=True)
return SodaDocument._from_impl(return_doc_impl) return SodaDocument._from_impl(return_doc_impl)
def truncate(self) -> None: def truncate(self) -> None:
@ -461,7 +462,7 @@ class SodaOperation:
""" """
return self._collection._impl.get_count(self) return self._collection._impl.get_count(self)
def fetchArraySize(self, value: int) -> Type["SodaOperation"]: def fetchArraySize(self, value: int) -> "SodaOperation":
""" """
This is a tuning method to specify the number of documents that are This is a tuning method to specify the number of documents that are
internally fetched in batches by calls to getCursor() and internally fetched in batches by calls to getCursor() and
@ -480,7 +481,7 @@ class SodaOperation:
self._fetch_array_size = value self._fetch_array_size = value
return self return self
def filter(self, value: Union[dict, str]) -> Type["SodaOperation"]: def filter(self, value: Union[dict, str]) -> "SodaOperation":
""" """
Sets a filter specification for complex document queries and ordering Sets a filter specification for complex document queries and ordering
of JSON documents. Filter specifications must be provided as a of JSON documents. Filter specifications must be provided as a
@ -499,7 +500,7 @@ class SodaOperation:
raise TypeError("expecting string or dictionary") raise TypeError("expecting string or dictionary")
return self return self
def getCursor(self) -> Type["SodaDocCursor"]: def getCursor(self) -> "SodaDocCursor":
""" """
Returns a SodaDocCursor object that can be used to iterate over the Returns a SodaDocCursor object that can be used to iterate over the
documents that match the criteria. documents that match the criteria.
@ -513,7 +514,7 @@ class SodaOperation:
""" """
return [d for d in self.getCursor()] return [d for d in self.getCursor()]
def getOne(self) -> Union[Type["SodaDocument"], None]: def getOne(self) -> Union["SodaDocument", None]:
""" """
Returns a single SodaDocument object that matches the criteria. Note Returns a single SodaDocument object that matches the criteria. Note
that if multiple documents match the criteria only the first one is that if multiple documents match the criteria only the first one is
@ -523,7 +524,7 @@ class SodaOperation:
if doc_impl is not None: if doc_impl is not None:
return SodaDocument._from_impl(doc_impl) return SodaDocument._from_impl(doc_impl)
def hint(self, value: str) -> Type["SodaOperation"]: def hint(self, value: str) -> "SodaOperation":
""" """
Specifies a hint that will be provided to the SODA operation when it is Specifies a hint that will be provided to the SODA operation when it is
performed. This is expected to be a string in the same format as SQL performed. This is expected to be a string in the same format as SQL
@ -541,7 +542,7 @@ class SodaOperation:
self._hint = value self._hint = value
return self return self
def key(self, value: str) -> Type["SodaOperation"]: def key(self, value: str) -> "SodaOperation":
""" """
Specifies that the document with the specified key should be returned. Specifies that the document with the specified key should be returned.
This causes any previous calls made to this method and keys() to be This causes any previous calls made to this method and keys() to be
@ -556,7 +557,7 @@ class SodaOperation:
self._keys = None self._keys = None
return self return self
def keys(self, value: list) -> Type["SodaOperation"]: def keys(self, value: list) -> "SodaOperation":
""" """
Specifies that documents that match the keys found in the supplied Specifies that documents that match the keys found in the supplied
sequence should be returned. This causes any previous calls made to sequence should be returned. This causes any previous calls made to
@ -573,7 +574,7 @@ class SodaOperation:
self._key = None self._key = None
return self return self
def limit(self, value: int) -> Type["SodaOperation"]: def limit(self, value: int) -> "SodaOperation":
""" """
Specifies that only the specified number of documents should be Specifies that only the specified number of documents should be
returned. This method is only usable for read operations such as returned. This method is only usable for read operations such as
@ -609,7 +610,7 @@ class SodaOperation:
return self._collection._impl.replace_one(self, doc_impl, return self._collection._impl.replace_one(self, doc_impl,
return_doc=False) return_doc=False)
def replaceOneAndGet(self, doc: object) -> Type["SodaDocument"]: def replaceOneAndGet(self, doc: object) -> "SodaDocument":
""" """
Similarly to replaceOne(), this method replaces a single document in Similarly to replaceOne(), this method replaces a single document in
the collection with the specified document. The only difference is that the collection with the specified document. The only difference is that
@ -621,7 +622,7 @@ class SodaOperation:
return_doc=True) return_doc=True)
return SodaDocument._from_impl(return_doc_impl) return SodaDocument._from_impl(return_doc_impl)
def skip(self, value: int) -> Type["SodaOperation"]: def skip(self, value: int) -> "SodaOperation":
""" """
Specifies the number of documents that match the other criteria that Specifies the number of documents that match the other criteria that
will be skipped. This method is only usable for read operations such as will be skipped. This method is only usable for read operations such as
@ -636,7 +637,7 @@ class SodaOperation:
self._skip = value self._skip = value
return self return self
def version(self, value: str) -> Type["SodaOperation"]: def version(self, value: str) -> "SodaOperation":
""" """
Specifies that documents with the specified version should be returned. Specifies that documents with the specified version should be returned.
Typically this is used with key() to implement optimistic locking, so Typically this is used with key() to implement optimistic locking, so

View File

@ -30,7 +30,7 @@
# events are detected. # events are detected.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
from typing import Callable, Type, Union from typing import Callable, Union, List
from . import connection from . import connection
class Subscription: class Subscription:
@ -53,7 +53,7 @@ class Subscription:
return self._impl.callback return self._impl.callback
@property @property
def connection(self) -> Type["connection.Connection"]: def connection(self) -> "connection.Connection":
""" """
Returns the connection that was used to register the subscription when Returns the connection that was used to register the subscription when
it was created. it was created.
@ -172,7 +172,7 @@ class Message:
@property @property
def consumer_name(self) -> str: def consumer_name(self) -> Union[str, None]:
""" """
Returns the name of the consumer which generated the notification. It Returns the name of the consumer which generated the notification. It
will be populated if the subscription was created with the namespace will be populated if the subscription was created with the namespace
@ -181,28 +181,29 @@ class Message:
return self._consumer_name return self._consumer_name
@property @property
def consumerName(self) -> str: def consumerName(self) -> Union[str, None]:
""" """
Deprecated. Use property consumer_name instead. Deprecated. Use property consumer_name instead.
""" """
return self.consumer_name return self.consumer_name
@property @property
def dbname(self) -> str: def dbname(self) -> Union[str, None]:
""" """
Returns the name of the database that generated the notification. Returns the name of the database that generated the notification.
""" """
return self._db_name return self._db_name
@property @property
def msgid(self) -> bytes: def msgid(self) -> Union[bytes, None]:
""" """
Returns the message id of the AQ message that generated the notification. Returns the message id of the AQ message that generated the
notification.
""" """
return self._msgid return self._msgid
@property @property
def queries(self) -> list: def queries(self) -> List["MessageQuery"]:
""" """
Returns a list of message query objects that give information about Returns a list of message query objects that give information about
query result sets changed for this notification. This attribute will be query result sets changed for this notification. This attribute will be
@ -212,7 +213,7 @@ class Message:
return self._queries return self._queries
@property @property
def queue_name(self) -> str: def queue_name(self) -> Union[str, None]:
""" """
Returns the name of the queue which generated the notification. It will Returns the name of the queue which generated the notification. It will
only be populated if the subscription was created with the namespace only be populated if the subscription was created with the namespace
@ -221,7 +222,7 @@ class Message:
return self._queue_name return self._queue_name
@property @property
def queueName(self) -> str: def queueName(self) -> Union[str, None]:
""" """
Deprecated. Use property queue_name instead. Deprecated. Use property queue_name instead.
""" """
@ -247,7 +248,7 @@ class Message:
return self._subscription return self._subscription
@property @property
def tables(self) -> list: def tables(self) -> List["MessageTable"]:
""" """
Returns a list of message table objects that give information about the Returns a list of message table objects that give information about the
tables changed for this notification. This attribute will be an empty tables changed for this notification. This attribute will be an empty
@ -257,14 +258,14 @@ class Message:
return self._tables return self._tables
@property @property
def txid(self) -> bytes: def txid(self) -> Union[bytes, None]:
""" """
Returns the id of the transaction that generated the notification. Returns the id of the transaction that generated the notification.
""" """
return self._txid return self._txid
@property @property
def type(self) -> str: def type(self) -> int:
""" """
Returns the type of message that has been sent. Returns the type of message that has been sent.
""" """
@ -297,7 +298,7 @@ class MessageQuery:
return self._operation return self._operation
@property @property
def tables(self) -> list: def tables(self) -> List["MessageTable"]:
""" """
Returns a list of message table objects that give information about the Returns a list of message table objects that give information about the
table changes that caused the query result set to change for this table changes that caused the query result set to change for this
@ -320,7 +321,7 @@ class MessageRow:
return self._operation return self._operation
@property @property
def rowid(self) -> str: def rowid(self) -> Union[str, None]:
""" """
Returns the rowid of the row that was changed. Returns the rowid of the row that was changed.
""" """
@ -335,7 +336,7 @@ class MessageTable:
self._rows = [] self._rows = []
@property @property
def name(self) -> str: def name(self) -> Union[str, None]:
""" """
Returns the name of the table that was changed. Returns the name of the table that was changed.
""" """
@ -349,7 +350,7 @@ class MessageTable:
return self._operation return self._operation
@property @property
def rows(self) -> list: def rows(self) -> List["MessageRow"]:
""" """
Returns a list of message row objects that give information about the Returns a list of message row objects that give information about the
rows changed on the table. This value is only filled in if the qos rows changed on the table. This value is only filled in if the qos

View File

@ -30,4 +30,4 @@
# file doc/src/conf.py both reference this file directly. # file doc/src/conf.py both reference this file directly.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
__version__ = "1.1.0" __version__ = "1.1.1"

View File

@ -194,5 +194,10 @@ class TestCase(test_env.BaseTestCase):
"1528 - test oracledb.ROWID pickling" "1528 - test oracledb.ROWID pickling"
self.__test_pickle(oracledb.ROWID) self.__test_pickle(oracledb.ROWID)
def test_1529_DB_TYPE_UROWID(self):
"1529 - test oracledb.DB_TYPE_UROWID comparisons and pickling"
self.__test_compare(oracledb.DB_TYPE_UROWID, oracledb.ROWID)
self.__test_pickle(oracledb.DB_TYPE_UROWID)
if __name__ == "__main__": if __name__ == "__main__":
test_env.run_test_cases() test_env.run_test_cases()

View File

@ -27,6 +27,7 @@
""" """
import datetime import datetime
import unittest
import oracledb import oracledb
import test_env import test_env
@ -84,5 +85,90 @@ class TestCase(test_env.BaseTestCase):
val=rowid) val=rowid)
self.assertEqual(self.cursor.fetchall(), [(int_val,)]) self.assertEqual(self.cursor.fetchall(), [(int_val,)])
def test_2904_select_rowids_as_rowids(self):
"2904 - test selecting regular rowids stored in a rowid column"
self.cursor.execute("truncate table TestRowids")
self.cursor.execute("""
insert into TestRowids (IntCol, RowidCol)
select IntCol, rowid from TestNumbers""")
self.connection.commit()
self.cursor.execute("select IntCol, RowidCol from TestRowids")
for int_val, rowid in self.cursor.fetchall():
self.cursor.execute("""
select IntCol
from TestNumbers
where rowid = :val""",
val=rowid)
self.assertEqual(self.cursor.fetchall(), [(int_val,)])
def test_2905_test_bind_and_insert_rowid(self):
"2905 - binding and inserting a rowid"
self.cursor.execute("truncate table TestRowids")
insert_data = [
(1, "String #1"), (2, "String #2"),
(3, "String #3"),(4, "String #4")
]
self.cursor.execute("truncate table TestTempTable")
sql = "insert into TestTempTable (IntCol, StringCol1) values (:1, :2)"
self.cursor.executemany(sql, insert_data)
self.connection.commit()
ridvar = self.cursor.var(oracledb.ROWID)
self.cursor.execute("""
begin
select rowid into :rid from TestTempTable
where IntCol = 3;
end;""",
rid=ridvar)
self.cursor.setinputsizes(r1=oracledb.ROWID)
self.cursor.execute("""
insert into TestRowids (IntCol, RowidCol)
values(1, :r1)""",
r1=ridvar)
self.connection.commit()
self.cursor.execute("select IntCol, RowidCol from TestRowids")
int_val, rowid = self.cursor.fetchone()
self.cursor.execute("""
select IntCol, StringCol1 from TestTempTable
where rowid = :val""",
val=rowid)
self.assertEqual(self.cursor.fetchone(), (3, "String #3"))
@unittest.skipIf(not test_env.get_is_thin(),
"thick mode doesn't support DB_TYPE_UROWID")
def test_2906_test_bind_and_insert_rowid_as_urowid(self):
"2906 - binding and inserting a rowid as urowid"
self.cursor.execute("truncate table TestRowids")
insert_data = [
(1, "String #1", datetime.datetime(2017, 4, 4)),
(2, "String #2", datetime.datetime(2017, 4, 5)),
(3, "String #3", datetime.datetime(2017, 4, 6)),
(4, "A" * 250, datetime.datetime(2017, 4, 7))
]
self.cursor.execute("truncate table TestUniversalRowids")
sql = "insert into TestUniversalRowids values (:1, :2, :3)"
self.cursor.executemany(sql, insert_data)
self.connection.commit()
ridvar = self.cursor.var(oracledb.DB_TYPE_UROWID)
self.cursor.execute("""
begin
select rowid into :rid from TestUniversalRowids
where IntCol = 3;
end;""",
rid=ridvar)
self.cursor.setinputsizes(r1=oracledb.DB_TYPE_UROWID)
self.cursor.execute("""
insert into TestRowids (IntCol, UrowidCol)
values(1, :r1)""",
r1=ridvar)
self.connection.commit()
self.cursor.execute("select IntCol, UrowidCol from TestRowids")
int_val, rowid = self.cursor.fetchone()
self.cursor.execute("""
select IntCol, StringCol, DateCol from TestUniversalRowids
where rowid = :val""",
val=rowid)
self.assertEqual(self.cursor.fetchone(),
(3, "String #3", datetime.datetime(2017, 4, 6)))
if __name__ == "__main__": if __name__ == "__main__":
test_env.run_test_cases() test_env.run_test_cases()

View File

@ -446,5 +446,46 @@ class TestCase(test_env.BaseTestCase):
result, = cursor.fetchone() result, = cursor.fetchone()
self.assertTrue(hint in result.read()) self.assertTrue(hint in result.read())
def test_3420_save_and_get(self):
"3420 - test saveAndGet"
soda_db = self.get_soda_database(minclient=(19, 9))
coll = soda_db.createCollection("TestSodaSaveAndGet")
coll.find().remove()
values_to_save = [
dict(name="John", age=50),
soda_db.createDocument(dict(name="Mark", age=45)),
soda_db.createDocument(dict(name="Jill", age=32))
]
inserted_keys = []
for value in values_to_save:
doc = coll.saveAndGet(value)
inserted_keys.append(doc.key)
fetched_docs = coll.find().getDocuments()
self.connection.commit()
self.assertEqual(coll.find().count(), len(values_to_save))
for key, fetched_doc in zip(inserted_keys, fetched_docs):
doc = coll.find().key(key).getOne()
self.assertEqual(doc.getContent(), fetched_doc.getContent())
coll.drop()
def test_3421_insert_many_and_get(self):
"3421 - test insert many and get"
soda_db = self.get_soda_database(minclient=(18, 5))
coll = soda_db.createCollection("TestInsertManyAndGet")
values_to_insert = [
dict(name="George", age=25),
soda_db.createDocument(dict(name="Lucas", age=47))
]
docs = coll.insertManyAndGet(values_to_insert)
inserted_keys = [i.key for i in docs]
self.connection.commit()
self.assertEqual(coll.find().count(), len(values_to_insert))
for key, expected_doc in zip(inserted_keys, values_to_insert):
if isinstance(expected_doc, dict):
expected_doc = soda_db.createDocument(expected_doc)
doc = coll.find().key(key).getOne()
self.assertEqual(doc.getContent(), expected_doc.getContent())
coll.drop()
if __name__ == "__main__": if __name__ == "__main__":
test_env.run_test_cases() test_env.run_test_cases()

View File

@ -395,5 +395,12 @@ class TestCase(test_env.BaseTestCase):
results = self.cursor.fetchall() results = self.cursor.fetchall()
self.assertEqual(results, [(None, None, None, None, None, None, None)]) self.assertEqual(results, [(None, None, None, None, None, None, None)])
@unittest.skipIf(not test_env.get_is_thin(),
"thick mode doesn't support DB_TYPE_UROWID")
def test_3725_DB_TYPE_UROWID(self):
"3725 - setting values on variables of type DB_TYPE_UROWID"
self._test_negative_set_and_get(oracledb.DB_TYPE_UROWID, 12345)
self._test_negative_set_and_get(oracledb.DB_TYPE_UROWID, "523lkhlf")
if __name__ == "__main__": if __name__ == "__main__":
test_env.run_test_cases() test_env.run_test_cases()

View File

@ -739,6 +739,7 @@ class TestCase(test_env.BaseTestCase):
admin_cursor.execute(sql) admin_cursor.execute(sql)
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-4011:", self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-4011:",
cursor.execute, "select user from dual") cursor.execute, "select user from dual")
self.assertFalse(conn.is_healthy())
def test_4359_kill_conn_in_context_manager(self): def test_4359_kill_conn_in_context_manager(self):
"4359 - kill connection in cursor context manager" "4359 - kill connection in cursor context manager"

View File

@ -32,7 +32,7 @@
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
import functools import functools
from typing import Type, Union, Callable from typing import Union, Callable
import oracledb import oracledb
@ -88,7 +88,7 @@ class ConnectParams:
#{{ params_properties }} #{{ params_properties }}
def copy(self) -> Type["ConnectParams"]: def copy(self) -> "ConnectParams":
""" """
Creates a copy of the parameters and returns it. Creates a copy of the parameters and returns it.
""" """

View File

@ -62,7 +62,7 @@ class Connection:
def __init__(self, def __init__(self,
dsn: str=None, *, dsn: str=None, *,
pool: Type["pool_module.ConnectionPool"]=None, pool: "pool_module.ConnectionPool"=None,
params: ConnectParams=None, params: ConnectParams=None,
**kwargs) -> None: **kwargs) -> None:
""" """
@ -97,51 +97,52 @@ class Connection:
self._impl = None self._impl = None
# determine if thin mode is being used # determine if thin mode is being used
thin = driver_mode.check_and_return_mode() with driver_mode.get_manager() as mode_mgr:
thin = mode_mgr.thin
# determine which connection parameters to use # determine which connection parameters to use
if params is None: if params is None:
params_impl = base_impl.ConnectParamsImpl() params_impl = base_impl.ConnectParamsImpl()
elif not isinstance(params, ConnectParams): elif not isinstance(params, ConnectParams):
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS) errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
else:
params_impl = params._impl.copy()
if kwargs:
params_impl.set(kwargs)
if dsn is not None:
dsn = params_impl.parse_dsn(dsn, thin)
if dsn is None:
dsn = params_impl.get_connect_string()
# see if connection is being acquired from a pool
if pool is None:
pool_impl = None
elif not isinstance(pool, pool_module.ConnectionPool):
message = "pool must be an instance of oracledb.ConnectionPool"
raise TypeError(message)
else:
pool._verify_open()
pool_impl = pool._impl
# create thin or thick implementation object
if thin:
if pool is not None:
impl = pool_impl.acquire(params_impl)
else: else:
impl = thin_impl.ThinConnImpl(dsn, params_impl) params_impl = params._impl.copy()
impl.connect(params_impl) if kwargs:
else: params_impl.set(kwargs)
impl = thick_impl.ThickConnImpl(dsn, params_impl) if dsn is not None:
impl.connect(params_impl, pool_impl) dsn = params_impl.parse_dsn(dsn, thin)
self._impl = impl if dsn is None:
self._version = None dsn = params_impl.get_connect_string()
# invoke callback, if applicable # see if connection is being acquired from a pool
if impl.invoke_session_callback and pool is not None \ if pool is None:
and pool.session_callback is not None \ pool_impl = None
and callable(pool.session_callback): elif not isinstance(pool, pool_module.ConnectionPool):
pool.session_callback(self, params_impl.tag) message = "pool must be an instance of oracledb.ConnectionPool"
impl.invoke_session_callback = False raise TypeError(message)
else:
pool._verify_open()
pool_impl = pool._impl
# create thin or thick implementation object
if thin:
if pool is not None:
impl = pool_impl.acquire(params_impl)
else:
impl = thin_impl.ThinConnImpl(dsn, params_impl)
impl.connect(params_impl)
else:
impl = thick_impl.ThickConnImpl(dsn, params_impl)
impl.connect(params_impl, pool_impl)
self._impl = impl
self._version = None
# invoke callback, if applicable
if impl.invoke_session_callback and pool is not None \
and pool.session_callback is not None \
and callable(pool.session_callback):
pool.session_callback(self, params_impl.tag)
impl.invoke_session_callback = False
def __del__(self): def __del__(self):
if self._impl is not None: if self._impl is not None:
@ -500,7 +501,7 @@ class Connection:
This function performs a local check. To fully check a connection's This function performs a local check. To fully check a connection's
health, use ping() which performs a round-trip to the database. health, use ping() which performs a round-trip to the database.
""" """
return self._impl.get_is_healthy() return self._impl is not None and self._impl.get_is_healthy()
@property @property
def ltxid(self) -> bytes: def ltxid(self) -> bytes:
@ -601,7 +602,7 @@ class Connection:
""" """
return self.tpc_prepare() return self.tpc_prepare()
def queue(self, name: str, payload_type: [DbObjectType, str]=None, *, def queue(self, name: str, payload_type: Union[DbObjectType, str]=None, *,
payloadType: DbObjectType=None) -> Queue: payloadType: DbObjectType=None) -> Queue:
""" """
Creates and returns a queue which is used to enqueue and dequeue Creates and returns a queue which is used to enqueue and dequeue
@ -999,7 +1000,7 @@ def _connection_factory(f):
""" """
@functools.wraps(f) @functools.wraps(f)
def connect(dsn: str=None, *, def connect(dsn: str=None, *,
pool: Type["pool_module.ConnectionPool"]=None, pool: "pool_module.ConnectionPool"=None,
conn_class: Type[Connection]=Connection, conn_class: Type[Connection]=Connection,
params: ConnectParams=None, params: ConnectParams=None,
**kwargs) -> Connection: **kwargs) -> Connection:
@ -1012,7 +1013,7 @@ def _connection_factory(f):
@_connection_factory @_connection_factory
def connect(dsn: str=None, *, def connect(dsn: str=None, *,
pool: Type["pool_module.ConnectionPool"]=None, pool: "pool_module.ConnectionPool"=None,
conn_class: Type[Connection]=Connection, conn_class: Type[Connection]=Connection,
params: ConnectParams=None, params: ConnectParams=None,
#{{ args_with_defaults }} #{{ args_with_defaults }}

View File

@ -86,17 +86,18 @@ class ConnectionPool:
params_impl.set(kwargs) params_impl.set(kwargs)
self._connection_type = \ self._connection_type = \
params_impl.connectiontype or connection_module.Connection params_impl.connectiontype or connection_module.Connection
thin = driver_mode.check_and_return_mode() with driver_mode.get_manager() as mode_mgr:
if dsn is not None: thin = mode_mgr.thin
dsn = params_impl.parse_dsn(dsn, thin) if dsn is not None:
if dsn is None: dsn = params_impl.parse_dsn(dsn, thin)
dsn = params_impl.get_connect_string() if dsn is None:
if thin: dsn = params_impl.get_connect_string()
impl = thin_impl.ThinPoolImpl(dsn, params_impl) if thin:
else: impl = thin_impl.ThinPoolImpl(dsn, params_impl)
impl = thick_impl.ThickPoolImpl(dsn, params_impl) else:
self._impl = impl impl = thick_impl.ThickPoolImpl(dsn, params_impl)
self.session_callback = params_impl.session_callback self._impl = impl
self.session_callback = params_impl.session_callback
def __del__(self): def __del__(self):
if self._impl is not None: if self._impl is not None:
@ -118,7 +119,7 @@ class ConnectionPool:
tag: str=None, tag: str=None,
matchanytag: bool=False, matchanytag: bool=False,
shardingkey: list=None, shardingkey: list=None,
supershardingkey: list=None) -> Type["connection_module.Connection"]: supershardingkey: list=None) -> "connection_module.Connection":
""" """
Acquire a connection from the pool and return it. Acquire a connection from the pool and return it.
@ -175,7 +176,7 @@ class ConnectionPool:
self._impl.close(force) self._impl.close(force)
self._impl = None self._impl = None
def drop(self, connection: Type["connection_module.Connection"]) -> None: def drop(self, connection: "connection_module.Connection") -> None:
""" """
Drop the connection from the pool, which is useful if the connection is Drop the connection from the pool, which is useful if the connection is
no longer usable (such as when the database session is killed). no longer usable (such as when the database session is killed).
@ -320,7 +321,7 @@ class ConnectionPool:
def ping_interval(self, value: int) -> None: def ping_interval(self, value: int) -> None:
self._impl.set_ping_interval(value) self._impl.set_ping_interval(value)
def release(self, connection: Type["connection_module.Connection"], def release(self, connection: "connection_module.Connection",
tag: str=None) -> None: tag: str=None) -> None:
""" """
Release the connection back to the pool now, rather than whenever Release the connection back to the pool now, rather than whenever

View File

@ -66,7 +66,7 @@ class PoolParams(ConnectParams):
#{{ params_properties }} #{{ params_properties }}
def copy(self) -> Type["PoolParams"]: def copy(self) -> "PoolParams":
""" """
Creates a copy of the parameters and returns it. Creates a copy of the parameters and returns it.
""" """