Added support for binding and fetching data of type DB_TYPE_OBJECT
(#43).
This commit is contained in:
parent
dd62aeef25
commit
0acdfdb5ad
|
@ -2243,10 +2243,6 @@ when binding data.
|
||||||
Describes columns, attributes or array elements in a database that are an
|
Describes columns, attributes or array elements in a database that are an
|
||||||
instance of a named SQL or PL/SQL type.
|
instance of a named SQL or PL/SQL type.
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This type is not supported in python-oracledb Thin mode.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: DB_TYPE_RAW
|
.. data:: DB_TYPE_RAW
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ oracledb 1.2.0b1 (TBD)
|
||||||
Thin Mode Changes
|
Thin Mode Changes
|
||||||
+++++++++++++++++
|
+++++++++++++++++
|
||||||
|
|
||||||
|
#) Added support for binding and fetching data of type
|
||||||
|
:data:`~oracledb.DB_TYPE_OBJECT`. Note that some of the error codes and
|
||||||
|
messages have changed as a result (DPY errors are raised instead of ones
|
||||||
|
specific to ODPI-C and OCI).
|
||||||
#) Added support for using a wallet for one-way TLS connections, rather than
|
#) Added support for using a wallet for one-way TLS connections, rather than
|
||||||
requiring OS recognition of certificates
|
requiring OS recognition of certificates
|
||||||
(`issue 65 <https://github.com/oracle/python-oracledb/issues/65>`__).
|
(`issue 65 <https://github.com/oracle/python-oracledb/issues/65>`__).
|
||||||
|
|
|
@ -171,7 +171,7 @@ see :ref:`driverdiff` and :ref:`compatibility`.
|
||||||
- Yes
|
- Yes
|
||||||
- Yes
|
- Yes
|
||||||
* - SQL execution (see :ref:`sqlexecution`)
|
* - SQL execution (see :ref:`sqlexecution`)
|
||||||
- Yes - bind and fetch all types except BFILE, OBJECT, and JSON
|
- Yes - bind and fetch all types except BFILE and JSON
|
||||||
- Yes
|
- Yes
|
||||||
- Yes
|
- Yes
|
||||||
* - PL/SQL execution (see :ref:`plsqlexecution`)
|
* - PL/SQL execution (see :ref:`plsqlexecution`)
|
||||||
|
@ -191,7 +191,7 @@ see :ref:`driverdiff` and :ref:`compatibility`.
|
||||||
- Yes
|
- Yes
|
||||||
- Yes
|
- Yes
|
||||||
* - SQL and PL/SQL type and collections (see :ref:`fetchobjects`)
|
* - SQL and PL/SQL type and collections (see :ref:`fetchobjects`)
|
||||||
- No
|
- Yes
|
||||||
- Yes
|
- Yes
|
||||||
- Yes
|
- Yes
|
||||||
* - Query column metadata
|
* - Query column metadata
|
||||||
|
@ -518,7 +518,7 @@ values.
|
||||||
- n/a
|
- n/a
|
||||||
* - User-defined types (object type, VARRAY, records, collections, SDO_*types)
|
* - User-defined types (object type, VARRAY, records, collections, SDO_*types)
|
||||||
- DB_TYPE_OBJECT
|
- DB_TYPE_OBJECT
|
||||||
- Not supported in python-oracledb Thin mode
|
- Yes
|
||||||
- OBJECT of specific type
|
- OBJECT of specific type
|
||||||
|
|
||||||
Binding of contiguous PL/SQL Index-by BINARY_INTEGER arrays of string, number, and date are
|
Binding of contiguous PL/SQL Index-by BINARY_INTEGER arrays of string, number, and date are
|
||||||
|
|
|
@ -37,8 +37,9 @@
|
||||||
import oracledb
|
import oracledb
|
||||||
import sample_env
|
import sample_env
|
||||||
|
|
||||||
# this script is currently only supported in python-oracledb thick mode
|
# determine whether to use python-oracledb thin mode or thick mode
|
||||||
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
if not sample_env.get_is_thin():
|
||||||
|
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
||||||
|
|
||||||
connection = oracledb.connect(user=sample_env.get_main_user(),
|
connection = oracledb.connect(user=sample_env.get_main_user(),
|
||||||
password=sample_env.get_main_password(),
|
password=sample_env.get_main_password(),
|
||||||
|
|
|
@ -34,8 +34,9 @@
|
||||||
import oracledb
|
import oracledb
|
||||||
import sample_env
|
import sample_env
|
||||||
|
|
||||||
# this script is currently only supported in python-oracledb thick mode
|
# determine whether to use python-oracledb thin mode or thick mode
|
||||||
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
if not sample_env.get_is_thin():
|
||||||
|
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
||||||
|
|
||||||
connection = oracledb.connect(user=sample_env.get_main_user(),
|
connection = oracledb.connect(user=sample_env.get_main_user(),
|
||||||
password=sample_env.get_main_password(),
|
password=sample_env.get_main_password(),
|
||||||
|
|
|
@ -35,8 +35,9 @@ import datetime
|
||||||
import oracledb
|
import oracledb
|
||||||
import sample_env
|
import sample_env
|
||||||
|
|
||||||
# this script is currently only supported in python-oracledb thick mode
|
# determine whether to use python-oracledb thin mode or thick mode
|
||||||
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
if not sample_env.get_is_thin():
|
||||||
|
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
||||||
|
|
||||||
connection = oracledb.connect(user=sample_env.get_main_user(),
|
connection = oracledb.connect(user=sample_env.get_main_user(),
|
||||||
password=sample_env.get_main_password(),
|
password=sample_env.get_main_password(),
|
||||||
|
|
|
@ -49,8 +49,9 @@ import geopandas as gpd
|
||||||
import oracledb
|
import oracledb
|
||||||
import sample_env
|
import sample_env
|
||||||
|
|
||||||
# this script is currently only supported in python-oracledb thick mode
|
# determine whether to use python-oracledb thin mode or thick mode
|
||||||
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
if not sample_env.get_is_thin():
|
||||||
|
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
||||||
|
|
||||||
# create Oracle connection and cursor objects
|
# create Oracle connection and cursor objects
|
||||||
connection = oracledb.connect(user=sample_env.get_main_user(),
|
connection = oracledb.connect(user=sample_env.get_main_user(),
|
||||||
|
|
|
@ -41,8 +41,9 @@ import datetime
|
||||||
import oracledb
|
import oracledb
|
||||||
import sample_env
|
import sample_env
|
||||||
|
|
||||||
# this script is currently only supported in python-oracledb thick mode
|
# determine whether to use python-oracledb thin mode or thick mode
|
||||||
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
if not sample_env.get_is_thin():
|
||||||
|
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
|
||||||
|
|
||||||
connection = oracledb.connect(user=sample_env.get_main_user(),
|
connection = oracledb.connect(user=sample_env.get_main_user(),
|
||||||
password=sample_env.get_main_password(),
|
password=sample_env.get_main_password(),
|
||||||
|
|
|
@ -83,12 +83,16 @@ cdef class DbType:
|
||||||
readonly str name
|
readonly str name
|
||||||
readonly uint32_t default_size
|
readonly uint32_t default_size
|
||||||
uint32_t _buffer_size_factor
|
uint32_t _buffer_size_factor
|
||||||
|
str _ora_name
|
||||||
uint8_t _ora_type_num
|
uint8_t _ora_type_num
|
||||||
uint8_t _csfrm
|
uint8_t _csfrm
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef DbType _from_num(uint32_t num)
|
cdef DbType _from_num(uint32_t num)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef DbType _from_ora_name(str name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef DbType _from_ora_type_and_csfrm(uint8_t ora_type_num, uint8_t csfrm)
|
cdef DbType _from_ora_type_and_csfrm(uint8_t ora_type_num, uint8_t csfrm)
|
||||||
|
|
||||||
|
@ -231,6 +235,9 @@ cdef class BaseConnImpl:
|
||||||
public bint autocommit
|
public bint autocommit
|
||||||
public bint invoke_session_callback
|
public bint invoke_session_callback
|
||||||
|
|
||||||
|
cdef object _check_value(self, DbType dbtype, BaseDbObjectTypeImpl objtype,
|
||||||
|
object value, bint* is_ok)
|
||||||
|
|
||||||
|
|
||||||
cdef class BasePoolImpl:
|
cdef class BasePoolImpl:
|
||||||
cdef:
|
cdef:
|
||||||
|
@ -320,6 +327,7 @@ cdef class BaseVarImpl:
|
||||||
public uint32_t num_elements_in_array
|
public uint32_t num_elements_in_array
|
||||||
readonly DbType dbtype
|
readonly DbType dbtype
|
||||||
readonly BaseDbObjectTypeImpl objtype
|
readonly BaseDbObjectTypeImpl objtype
|
||||||
|
BaseConnImpl _conn_impl
|
||||||
int _preferred_num_type
|
int _preferred_num_type
|
||||||
FetchInfo _fetch_info
|
FetchInfo _fetch_info
|
||||||
bint _is_value_set
|
bint _is_value_set
|
||||||
|
|
|
@ -437,7 +437,7 @@ class Connection:
|
||||||
objects which can be bound to cursors created by this connection.
|
objects which can be bound to cursors created by this connection.
|
||||||
"""
|
"""
|
||||||
self._verify_connected()
|
self._verify_connected()
|
||||||
obj_type_impl = self._impl.get_type(name)
|
obj_type_impl = self._impl.get_type(self, name)
|
||||||
return DbObjectType._from_impl(obj_type_impl)
|
return DbObjectType._from_impl(obj_type_impl)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
|
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from . import errors
|
||||||
from . import __name__ as MODULE_NAME
|
from . import __name__ as MODULE_NAME
|
||||||
from .base_impl import DbType
|
from .base_impl import DbType
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ class DbObject:
|
||||||
return self._impl.get_attr_value(attr_impl)
|
return self._impl.get_attr_value(attr_impl)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<oracledb.Object {self.type.schema}.{self.type.name} at " \
|
return f"<oracledb.Object {self.type._get_full_name()} at " \
|
||||||
f"{hex(id(self))}>"
|
f"{hex(id(self))}>"
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
|
@ -52,6 +53,15 @@ class DbObject:
|
||||||
attr_impl = self._impl.type.attrs_by_name[name]
|
attr_impl = self._impl.type.attrs_by_name[name]
|
||||||
self._impl.set_attr_value(attr_impl, value)
|
self._impl.set_attr_value(attr_impl, value)
|
||||||
|
|
||||||
|
def _ensure_is_collection(self):
|
||||||
|
"""
|
||||||
|
Ensures that the object refers to a collection. If not, an exception is
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
if not self.type.iscollection:
|
||||||
|
errors._raise_err(errors.ERR_OBJECT_IS_NOT_A_COLLECTION,
|
||||||
|
name=self.type._get_full_name())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_impl(cls, impl):
|
def _from_impl(cls, impl):
|
||||||
obj = cls.__new__(cls)
|
obj = cls.__new__(cls)
|
||||||
|
@ -73,6 +83,7 @@ class DbObject:
|
||||||
Return a dictionary where the collection’s indexes are the keys and the
|
Return a dictionary where the collection’s indexes are the keys and the
|
||||||
elements are its values.
|
elements are its values.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
result = {}
|
result = {}
|
||||||
ix = self._impl.get_first_index()
|
ix = self._impl.get_first_index()
|
||||||
while ix is not None:
|
while ix is not None:
|
||||||
|
@ -84,6 +95,7 @@ class DbObject:
|
||||||
"""
|
"""
|
||||||
Return a list of each of the collection’s elements in index order.
|
Return a list of each of the collection’s elements in index order.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
result = []
|
result = []
|
||||||
ix = self._impl.get_first_index()
|
ix = self._impl.get_first_index()
|
||||||
while ix is not None:
|
while ix is not None:
|
||||||
|
@ -106,6 +118,7 @@ class DbObject:
|
||||||
not changed. In other words, the delete operation creates holes in the
|
not changed. In other words, the delete operation creates holes in the
|
||||||
collection.
|
collection.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
self._impl.delete_by_index(index)
|
self._impl.delete_by_index(index)
|
||||||
|
|
||||||
def exists(self, index: int) -> bool:
|
def exists(self, index: int) -> bool:
|
||||||
|
@ -113,6 +126,7 @@ class DbObject:
|
||||||
Return True or False indicating if an element exists in the collection
|
Return True or False indicating if an element exists in the collection
|
||||||
at the specified index.
|
at the specified index.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.exists_by_index(index)
|
return self._impl.exists_by_index(index)
|
||||||
|
|
||||||
def extend(self, seq: list) -> None:
|
def extend(self, seq: list) -> None:
|
||||||
|
@ -121,6 +135,7 @@ class DbObject:
|
||||||
the equivalent of performing append() for each element found in the
|
the equivalent of performing append() for each element found in the
|
||||||
sequence.
|
sequence.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
for value in seq:
|
for value in seq:
|
||||||
self.append(value)
|
self.append(value)
|
||||||
|
|
||||||
|
@ -129,6 +144,7 @@ class DbObject:
|
||||||
Return the index of the first element in the collection. If the
|
Return the index of the first element in the collection. If the
|
||||||
collection is empty, None is returned.
|
collection is empty, None is returned.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.get_first_index()
|
return self._impl.get_first_index()
|
||||||
|
|
||||||
def getelement(self, index: int) -> object:
|
def getelement(self, index: int) -> object:
|
||||||
|
@ -136,6 +152,7 @@ class DbObject:
|
||||||
Return the element at the specified index of the collection. If no
|
Return the element at the specified index of the collection. If no
|
||||||
element exists at that index, an exception is raised.
|
element exists at that index, an exception is raised.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.get_element_by_index(index)
|
return self._impl.get_element_by_index(index)
|
||||||
|
|
||||||
def last(self) -> int:
|
def last(self) -> int:
|
||||||
|
@ -143,6 +160,7 @@ class DbObject:
|
||||||
Return the index of the last element in the collection. If the
|
Return the index of the last element in the collection. If the
|
||||||
collection is empty, None is returned.
|
collection is empty, None is returned.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.get_last_index()
|
return self._impl.get_last_index()
|
||||||
|
|
||||||
def next(self, index: int) -> int:
|
def next(self, index: int) -> int:
|
||||||
|
@ -151,6 +169,7 @@ class DbObject:
|
||||||
specified index. If there are no elements in the collection following
|
specified index. If there are no elements in the collection following
|
||||||
the specified index, None is returned.
|
the specified index, None is returned.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.get_next_index(index)
|
return self._impl.get_next_index(index)
|
||||||
|
|
||||||
def prev(self, index: int) -> int:
|
def prev(self, index: int) -> int:
|
||||||
|
@ -159,6 +178,7 @@ class DbObject:
|
||||||
specified index. If there are no elements in the collection preceding
|
specified index. If there are no elements in the collection preceding
|
||||||
the specified index, None is returned.
|
the specified index, None is returned.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.get_prev_index(index)
|
return self._impl.get_prev_index(index)
|
||||||
|
|
||||||
def setelement(self, index: int, value: object) -> None:
|
def setelement(self, index: int, value: object) -> None:
|
||||||
|
@ -166,18 +186,21 @@ class DbObject:
|
||||||
Set the value in the collection at the specified index to the given
|
Set the value in the collection at the specified index to the given
|
||||||
value.
|
value.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
self._impl.set_element_by_index(index, value)
|
self._impl.set_element_by_index(index, value)
|
||||||
|
|
||||||
def size(self) -> int:
|
def size(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return the number of elements in the collection.
|
Return the number of elements in the collection.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
return self._impl.get_size()
|
return self._impl.get_size()
|
||||||
|
|
||||||
def trim(self, num: int) -> None:
|
def trim(self, num: int) -> None:
|
||||||
"""
|
"""
|
||||||
Remove the specified number of elements from the end of the collection.
|
Remove the specified number of elements from the end of the collection.
|
||||||
"""
|
"""
|
||||||
|
self._ensure_is_collection()
|
||||||
self._impl.trim(num)
|
self._impl.trim(num)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -237,11 +260,7 @@ class DbObjectType:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.package_name is not None:
|
return f"<oracledb.DbObjectType {self._get_full_name()}>"
|
||||||
full_name = f"{self.schema}.{self.package_name}.{self.name}"
|
|
||||||
else:
|
|
||||||
full_name = f"{self.schema}.{self.name}"
|
|
||||||
return f"<oracledb.DbObjectType {full_name}>"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_impl(cls, impl):
|
def _from_impl(cls, impl):
|
||||||
|
@ -251,6 +270,14 @@ class DbObjectType:
|
||||||
typ._element_type = None
|
typ._element_type = None
|
||||||
return typ
|
return typ
|
||||||
|
|
||||||
|
def _get_full_name(self):
|
||||||
|
"""
|
||||||
|
Returns the full name of the type.
|
||||||
|
"""
|
||||||
|
if self.package_name is not None:
|
||||||
|
return f"{self.schema}.{self.package_name}.{self.name}"
|
||||||
|
return f"{self.schema}.{self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attributes(self) -> list:
|
def attributes(self) -> list:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -166,13 +166,15 @@ ERR_INVALID_ACCESS_TOKEN_PARAM = 2031
|
||||||
ERR_INVALID_ACCESS_TOKEN_RETURNED = 2032
|
ERR_INVALID_ACCESS_TOKEN_RETURNED = 2032
|
||||||
ERR_EXPIRED_ACCESS_TOKEN = 2033
|
ERR_EXPIRED_ACCESS_TOKEN = 2033
|
||||||
ERR_ACCESS_TOKEN_REQUIRES_TCPS = 2034
|
ERR_ACCESS_TOKEN_REQUIRES_TCPS = 2034
|
||||||
|
ERR_INVALID_OBJECT_TYPE_NAME = 2035
|
||||||
|
ERR_OBJECT_IS_NOT_A_COLLECTION = 2036
|
||||||
|
|
||||||
# error numbers that result in NotSupportedError
|
# error numbers that result in NotSupportedError
|
||||||
ERR_TIME_NOT_SUPPORTED = 3000
|
ERR_TIME_NOT_SUPPORTED = 3000
|
||||||
ERR_FEATURE_NOT_SUPPORTED = 3001
|
ERR_FEATURE_NOT_SUPPORTED = 3001
|
||||||
ERR_PYTHON_VALUE_NOT_SUPPORTED = 3002
|
ERR_PYTHON_VALUE_NOT_SUPPORTED = 3002
|
||||||
ERR_PYTHON_TYPE_NOT_SUPPORTED = 3003
|
ERR_PYTHON_TYPE_NOT_SUPPORTED = 3003
|
||||||
ERR_UNSUPPORTED_VAR_SET = 3004
|
ERR_UNSUPPORTED_TYPE_SET = 3004
|
||||||
ERR_ARRAYS_OF_ARRAYS = 3005
|
ERR_ARRAYS_OF_ARRAYS = 3005
|
||||||
ERR_ORACLE_TYPE_NOT_SUPPORTED = 3006
|
ERR_ORACLE_TYPE_NOT_SUPPORTED = 3006
|
||||||
ERR_DB_TYPE_NOT_SUPPORTED = 3007
|
ERR_DB_TYPE_NOT_SUPPORTED = 3007
|
||||||
|
@ -180,10 +182,12 @@ ERR_UNSUPPORTED_INBAND_NOTIFICATION = 3008
|
||||||
ERR_SELF_BIND_NOT_SUPPORTED = 3009
|
ERR_SELF_BIND_NOT_SUPPORTED = 3009
|
||||||
ERR_SERVER_VERSION_NOT_SUPPORTED = 3010
|
ERR_SERVER_VERSION_NOT_SUPPORTED = 3010
|
||||||
ERR_NCHAR_CS_NOT_SUPPORTED = 3012
|
ERR_NCHAR_CS_NOT_SUPPORTED = 3012
|
||||||
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_VAR = 3013
|
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_DB_TYPE = 3013
|
||||||
ERR_LOB_OF_WRONG_TYPE = 3014
|
ERR_LOB_OF_WRONG_TYPE = 3014
|
||||||
ERR_UNSUPPORTED_VERIFIER_TYPE = 3015
|
ERR_UNSUPPORTED_VERIFIER_TYPE = 3015
|
||||||
ERR_NO_CRYPTOGRAPHY_PACKAGE = 3016
|
ERR_NO_CRYPTOGRAPHY_PACKAGE = 3016
|
||||||
|
ERR_ORACLE_TYPE_NAME_NOT_SUPPORTED = 3017
|
||||||
|
ERR_TDS_TYPE_NOT_SUPPORTED = 3018
|
||||||
|
|
||||||
# error numbers that result in DatabaseError
|
# error numbers that result in DatabaseError
|
||||||
ERR_TNS_ENTRY_NOT_FOUND = 4000
|
ERR_TNS_ENTRY_NOT_FOUND = 4000
|
||||||
|
@ -221,6 +225,7 @@ ERR_INTEGER_TOO_LARGE = 5002
|
||||||
ERR_UNEXPECTED_NEGATIVE_INTEGER = 5003
|
ERR_UNEXPECTED_NEGATIVE_INTEGER = 5003
|
||||||
ERR_UNEXPECTED_DATA = 5004
|
ERR_UNEXPECTED_DATA = 5004
|
||||||
ERR_UNEXPECTED_REFUSE = 5005
|
ERR_UNEXPECTED_REFUSE = 5005
|
||||||
|
ERR_UNEXPECTED_END_OF_DATA = 5006
|
||||||
|
|
||||||
# error numbers that result in OperationalError
|
# error numbers that result in OperationalError
|
||||||
ERR_LISTENER_REFUSED_CONNECTION = 6000
|
ERR_LISTENER_REFUSED_CONNECTION = 6000
|
||||||
|
@ -235,6 +240,7 @@ ERR_ORACLE_ERROR_XREF = {
|
||||||
28: ERR_CONNECTION_CLOSED,
|
28: ERR_CONNECTION_CLOSED,
|
||||||
600: ERR_CONNECTION_CLOSED,
|
600: ERR_CONNECTION_CLOSED,
|
||||||
1005: ERR_NO_CREDENTIALS,
|
1005: ERR_NO_CREDENTIALS,
|
||||||
|
22303: (ERR_INVALID_OBJECT_TYPE_NAME, 'type "(?P<name>[^"]*"."[^"]*)"'),
|
||||||
24422: ERR_POOL_HAS_BUSY_CONNECTIONS,
|
24422: ERR_POOL_HAS_BUSY_CONNECTIONS,
|
||||||
24349: ERR_ARRAY_DML_ROW_COUNTS_NOT_ENABLED,
|
24349: ERR_ARRAY_DML_ROW_COUNTS_NOT_ENABLED,
|
||||||
24459: ERR_POOL_NO_CONNECTION_AVAILABLE,
|
24459: ERR_POOL_NO_CONNECTION_AVAILABLE,
|
||||||
|
@ -274,7 +280,7 @@ ERR_MESSAGE_FORMATS = {
|
||||||
ERR_ARRAYS_OF_ARRAYS:
|
ERR_ARRAYS_OF_ARRAYS:
|
||||||
'arrays of arrays are not supported',
|
'arrays of arrays are not supported',
|
||||||
ERR_BUFFER_LENGTH_INSUFFICIENT:
|
ERR_BUFFER_LENGTH_INSUFFICIENT:
|
||||||
'internal error: split buffer of length {actual_buffer_len} '
|
'internal error: buffer of length {actual_buffer_len} '
|
||||||
'insufficient to hold {required_buffer_len} bytes',
|
'insufficient to hold {required_buffer_len} bytes',
|
||||||
ERR_CALL_TIMEOUT_EXCEEDED:
|
ERR_CALL_TIMEOUT_EXCEEDED:
|
||||||
'call timeout of {timeout} ms exceeded',
|
'call timeout of {timeout} ms exceeded',
|
||||||
|
@ -338,6 +344,8 @@ ERR_MESSAGE_FORMATS = {
|
||||||
'"{name}" argument contains invalid values',
|
'"{name}" argument contains invalid values',
|
||||||
ERR_INVALID_NUMBER:
|
ERR_INVALID_NUMBER:
|
||||||
'invalid number',
|
'invalid number',
|
||||||
|
ERR_INVALID_OBJECT_TYPE_NAME:
|
||||||
|
'invalid object type name: "{name}"',
|
||||||
ERR_INVALID_OCI_ATTR_TYPE:
|
ERR_INVALID_OCI_ATTR_TYPE:
|
||||||
'invalid OCI attribute type {attr_type}',
|
'invalid OCI attribute type {attr_type}',
|
||||||
ERR_INVALID_POOL_CLASS:
|
ERR_INVALID_POOL_CLASS:
|
||||||
|
@ -414,8 +422,12 @@ ERR_MESSAGE_FORMATS = {
|
||||||
'invalid number: empty exponent',
|
'invalid number: empty exponent',
|
||||||
ERR_NUMBER_WITH_INVALID_EXPONENT:
|
ERR_NUMBER_WITH_INVALID_EXPONENT:
|
||||||
'invalid number: invalid exponent',
|
'invalid number: invalid exponent',
|
||||||
|
ERR_OBJECT_IS_NOT_A_COLLECTION:
|
||||||
|
'object {name} is not a collection',
|
||||||
ERR_ORACLE_NUMBER_NO_REPR:
|
ERR_ORACLE_NUMBER_NO_REPR:
|
||||||
'value cannot be represented as an Oracle number',
|
'value cannot be represented as an Oracle number',
|
||||||
|
ERR_ORACLE_TYPE_NAME_NOT_SUPPORTED:
|
||||||
|
'Oracle data type name "{name}" is not supported',
|
||||||
ERR_ORACLE_TYPE_NOT_SUPPORTED:
|
ERR_ORACLE_TYPE_NOT_SUPPORTED:
|
||||||
'Oracle data type {num} is not supported',
|
'Oracle data type {num} is not supported',
|
||||||
ERR_POOL_HAS_BUSY_CONNECTIONS:
|
ERR_POOL_HAS_BUSY_CONNECTIONS:
|
||||||
|
@ -437,6 +449,8 @@ ERR_MESSAGE_FORMATS = {
|
||||||
ERR_SERVER_VERSION_NOT_SUPPORTED:
|
ERR_SERVER_VERSION_NOT_SUPPORTED:
|
||||||
'connections to this database server version are not supported '
|
'connections to this database server version are not supported '
|
||||||
'by python-oracledb in thin mode',
|
'by python-oracledb in thin mode',
|
||||||
|
ERR_TDS_TYPE_NOT_SUPPORTED:
|
||||||
|
'Oracle TDS data type {num} is not supported',
|
||||||
ERR_THIN_CONNECTION_ALREADY_CREATED:
|
ERR_THIN_CONNECTION_ALREADY_CREATED:
|
||||||
'python-oracledb thick mode cannot be used because a thin mode '
|
'python-oracledb thick mode cannot be used because a thin mode '
|
||||||
'connection has already been created',
|
'connection has already been created',
|
||||||
|
@ -450,6 +464,9 @@ ERR_MESSAGE_FORMATS = {
|
||||||
'{config_dir}',
|
'{config_dir}',
|
||||||
ERR_UNEXPECTED_DATA:
|
ERR_UNEXPECTED_DATA:
|
||||||
'unexpected data received: {data}',
|
'unexpected data received: {data}',
|
||||||
|
ERR_UNEXPECTED_END_OF_DATA:
|
||||||
|
'unexpected end of data: want {num_bytes_wanted} bytes but '
|
||||||
|
'only {num_bytes_available} bytes are available',
|
||||||
ERR_UNEXPECTED_NEGATIVE_INTEGER:
|
ERR_UNEXPECTED_NEGATIVE_INTEGER:
|
||||||
'internal error: read a negative integer when expecting a '
|
'internal error: read a negative integer when expecting a '
|
||||||
'positive integer',
|
'positive integer',
|
||||||
|
@ -458,11 +475,11 @@ ERR_MESSAGE_FORMATS = {
|
||||||
'format was returned',
|
'format was returned',
|
||||||
ERR_UNSUPPORTED_INBAND_NOTIFICATION:
|
ERR_UNSUPPORTED_INBAND_NOTIFICATION:
|
||||||
'unsupported in-band notification with error number {err_num}',
|
'unsupported in-band notification with error number {err_num}',
|
||||||
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_VAR:
|
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_DB_TYPE:
|
||||||
'unsupported Python type {py_type_name} for variable '
|
'unsupported Python type {py_type_name} for database type '
|
||||||
'{db_type_name}',
|
'{db_type_name}',
|
||||||
ERR_UNSUPPORTED_VAR_SET:
|
ERR_UNSUPPORTED_TYPE_SET:
|
||||||
'variable of type {db_type_name} does not support being set',
|
'type {db_type_name} does not support being set',
|
||||||
ERR_UNSUPPORTED_VERIFIER_TYPE:
|
ERR_UNSUPPORTED_VERIFIER_TYPE:
|
||||||
'password verifier type 0x{verifier_type:x} is not supported by '
|
'password verifier type 0x{verifier_type:x} is not supported by '
|
||||||
'python-oracledb in thin mode',
|
'python-oracledb in thin mode',
|
||||||
|
|
|
@ -35,6 +35,119 @@ cdef class BaseConnImpl:
|
||||||
self.dsn = dsn
|
self.dsn = dsn
|
||||||
self.username = params.user
|
self.username = params.user
|
||||||
|
|
||||||
|
cdef object _check_value(self, DbType dbtype, BaseDbObjectTypeImpl objtype,
|
||||||
|
object value, bint* is_ok):
|
||||||
|
"""
|
||||||
|
Checks that the specified Python value is acceptable for the given
|
||||||
|
database type. If the "is_ok" parameter is passed as NULL, an exception
|
||||||
|
is raised. The value to use is returned (possibly modified from the
|
||||||
|
value passed in).
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint32_t db_type_num
|
||||||
|
BaseLobImpl lob_impl
|
||||||
|
|
||||||
|
# null values are always accepted
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
# check to see if the Python value is accepted and perform any
|
||||||
|
# necessary adjustments
|
||||||
|
db_type_num = dbtype.num
|
||||||
|
if db_type_num in (DB_TYPE_NUM_NUMBER,
|
||||||
|
DB_TYPE_NUM_BINARY_INTEGER,
|
||||||
|
DB_TYPE_NUM_BINARY_DOUBLE,
|
||||||
|
DB_TYPE_NUM_BINARY_FLOAT):
|
||||||
|
if isinstance(value, (PY_TYPE_BOOL, int, float, PY_TYPE_DECIMAL)):
|
||||||
|
if db_type_num in (DB_TYPE_NUM_BINARY_FLOAT,
|
||||||
|
DB_TYPE_NUM_BINARY_DOUBLE):
|
||||||
|
return float(value)
|
||||||
|
elif db_type_num == DB_TYPE_NUM_BINARY_INTEGER \
|
||||||
|
or cpython.PyBool_Check(value):
|
||||||
|
return int(value)
|
||||||
|
return value
|
||||||
|
elif db_type_num in (DB_TYPE_NUM_CHAR,
|
||||||
|
DB_TYPE_NUM_VARCHAR,
|
||||||
|
DB_TYPE_NUM_NCHAR,
|
||||||
|
DB_TYPE_NUM_NVARCHAR,
|
||||||
|
DB_TYPE_NUM_LONG_VARCHAR,
|
||||||
|
DB_TYPE_NUM_LONG_NVARCHAR):
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
return (<bytes> value).decode()
|
||||||
|
elif isinstance(value, str):
|
||||||
|
return value
|
||||||
|
elif db_type_num in (DB_TYPE_NUM_RAW, DB_TYPE_NUM_LONG_RAW):
|
||||||
|
if isinstance(value, str):
|
||||||
|
return (<str> value).encode()
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
return value
|
||||||
|
elif db_type_num in (DB_TYPE_NUM_DATE,
|
||||||
|
DB_TYPE_NUM_TIMESTAMP,
|
||||||
|
DB_TYPE_NUM_TIMESTAMP_LTZ,
|
||||||
|
DB_TYPE_NUM_TIMESTAMP_TZ):
|
||||||
|
if cydatetime.PyDateTime_Check(value) \
|
||||||
|
or cydatetime.PyDate_Check(value):
|
||||||
|
return value
|
||||||
|
elif db_type_num == DB_TYPE_NUM_INTERVAL_DS:
|
||||||
|
if isinstance(value, PY_TYPE_TIMEDELTA):
|
||||||
|
return value
|
||||||
|
elif db_type_num in (DB_TYPE_NUM_CLOB,
|
||||||
|
DB_TYPE_NUM_NCLOB,
|
||||||
|
DB_TYPE_NUM_BLOB):
|
||||||
|
if isinstance(value, PY_TYPE_LOB):
|
||||||
|
lob_impl = value._impl
|
||||||
|
if lob_impl.dbtype is not dbtype:
|
||||||
|
if is_ok != NULL:
|
||||||
|
is_ok[0] = False
|
||||||
|
return value
|
||||||
|
errors._raise_err(errors.ERR_LOB_OF_WRONG_TYPE,
|
||||||
|
actual_type_name=lob_impl.dbtype.name,
|
||||||
|
expected_type_name=dbtype.name)
|
||||||
|
return value
|
||||||
|
elif isinstance(value, (bytes, str)):
|
||||||
|
if db_type_num == DB_TYPE_NUM_BLOB:
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.encode()
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
value = value.decode()
|
||||||
|
lob_impl = self.create_temp_lob_impl(dbtype)
|
||||||
|
if value:
|
||||||
|
lob_impl.write(value, 1)
|
||||||
|
return PY_TYPE_LOB._from_impl(lob_impl)
|
||||||
|
elif db_type_num == DB_TYPE_NUM_OBJECT:
|
||||||
|
if isinstance(value, PY_TYPE_DB_OBJECT):
|
||||||
|
if value._impl.type != objtype:
|
||||||
|
if is_ok != NULL:
|
||||||
|
is_ok[0] = False
|
||||||
|
return value
|
||||||
|
errors._raise_err(errors.ERR_WRONG_OBJECT_TYPE,
|
||||||
|
actual_schema=value.type.schema,
|
||||||
|
actual_name=value.type.name,
|
||||||
|
expected_schema=objtype.schema,
|
||||||
|
expected_name=objtype.name)
|
||||||
|
return value
|
||||||
|
elif db_type_num == DB_TYPE_NUM_CURSOR:
|
||||||
|
if isinstance(value, PY_TYPE_CURSOR):
|
||||||
|
return value
|
||||||
|
elif db_type_num == DB_TYPE_NUM_BOOLEAN:
|
||||||
|
return bool(value)
|
||||||
|
elif db_type_num == DB_TYPE_NUM_JSON:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
if is_ok != NULL:
|
||||||
|
is_ok[0] = False
|
||||||
|
return value
|
||||||
|
errors._raise_err(errors.ERR_UNSUPPORTED_TYPE_SET,
|
||||||
|
db_type_name=dbtype.name)
|
||||||
|
|
||||||
|
# the Python value was not considered acceptable
|
||||||
|
if is_ok != NULL:
|
||||||
|
is_ok[0] = False
|
||||||
|
return value
|
||||||
|
errors._raise_err(errors.ERR_UNSUPPORTED_PYTHON_TYPE_FOR_DB_TYPE,
|
||||||
|
py_type_name=type(value).__name__,
|
||||||
|
db_type_name=dbtype.name)
|
||||||
|
|
||||||
@utils.CheckImpls("getting a connection OCI attribute")
|
@utils.CheckImpls("getting a connection OCI attribute")
|
||||||
def _get_oci_attr(self, uint32_t handle_type, uint32_t attr_num,
|
def _get_oci_attr(self, uint32_t handle_type, uint32_t attr_num,
|
||||||
uint32_t attr_type):
|
uint32_t attr_type):
|
||||||
|
@ -123,7 +236,7 @@ cdef class BaseConnImpl:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@utils.CheckImpls("getting an object type")
|
@utils.CheckImpls("getting an object type")
|
||||||
def get_type(self, str name):
|
def get_type(self, object conn, str name):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@utils.CheckImpls("getting the database version")
|
@utils.CheckImpls("getting the database version")
|
||||||
|
|
|
@ -31,8 +31,18 @@
|
||||||
|
|
||||||
cdef class BaseDbObjectImpl:
|
cdef class BaseDbObjectImpl:
|
||||||
|
|
||||||
@utils.CheckImpls("appending a value to a collection")
|
|
||||||
def append(self, object value):
|
def append(self, object value):
|
||||||
|
"""
|
||||||
|
Appends a value to the collection after first checking to see if the
|
||||||
|
value is acceptable.
|
||||||
|
"""
|
||||||
|
cdef BaseConnImpl conn_impl = self.type._conn_impl
|
||||||
|
value = conn_impl._check_value(self.type.element_dbtype,
|
||||||
|
self.type.element_objtype, value, NULL)
|
||||||
|
self.append_checked(value)
|
||||||
|
|
||||||
|
@utils.CheckImpls("appending a value to a collection")
|
||||||
|
def append_checked(self, object value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@utils.CheckImpls("creating a copy of an object")
|
@utils.CheckImpls("creating a copy of an object")
|
||||||
|
@ -75,12 +85,31 @@ cdef class BaseDbObjectImpl:
|
||||||
def get_size(self):
|
def get_size(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@utils.CheckImpls("setting an attribute value")
|
|
||||||
def set_attr_value(self, BaseDbObjectAttrImpl attr, object value):
|
def set_attr_value(self, BaseDbObjectAttrImpl attr, object value):
|
||||||
|
"""
|
||||||
|
Sets the attribute value after first checking to see if the value is
|
||||||
|
acceptable.
|
||||||
|
"""
|
||||||
|
cdef BaseConnImpl conn_impl = self.type._conn_impl
|
||||||
|
value = conn_impl._check_value(attr.dbtype, attr.objtype, value, NULL)
|
||||||
|
self.set_attr_value_checked(attr, value)
|
||||||
|
|
||||||
|
@utils.CheckImpls("setting an attribute value")
|
||||||
|
def set_attr_value_checked(self, BaseDbObjectAttrImpl attr, object value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@utils.CheckImpls("setting an element of a collection")
|
|
||||||
def set_element_by_index(self, int32_t index, object value):
|
def set_element_by_index(self, int32_t index, object value):
|
||||||
|
"""
|
||||||
|
Sets the element value after first checking to see if the value is
|
||||||
|
acceptable.
|
||||||
|
"""
|
||||||
|
cdef BaseConnImpl conn_impl = self.type._conn_impl
|
||||||
|
value = conn_impl._check_value(self.type.element_dbtype,
|
||||||
|
self.type.element_objtype, value, NULL)
|
||||||
|
self.set_element_by_index_checked(index, value)
|
||||||
|
|
||||||
|
@utils.CheckImpls("setting an element of a collection")
|
||||||
|
def set_element_by_index_checked(self, int32_t index, object value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@utils.CheckImpls("trimming elements from a collection")
|
@utils.CheckImpls("trimming elements from a collection")
|
||||||
|
|
|
@ -51,20 +51,23 @@ cdef class ApiType:
|
||||||
|
|
||||||
|
|
||||||
cdef dict db_type_by_num = {}
|
cdef dict db_type_by_num = {}
|
||||||
|
cdef dict db_type_by_ora_name = {}
|
||||||
cdef dict db_type_by_ora_type_num = {}
|
cdef dict db_type_by_ora_type_num = {}
|
||||||
|
|
||||||
cdef class DbType:
|
cdef class DbType:
|
||||||
|
|
||||||
def __init__(self, num, name, ora_type_num, default_size=0, csfrm=0,
|
def __init__(self, num, name, ora_name, ora_type_num, default_size=0,
|
||||||
buffer_size_factor=0):
|
csfrm=0, buffer_size_factor=0):
|
||||||
cdef uint16_t ora_type_key = csfrm * 256 + ora_type_num
|
cdef uint16_t ora_type_key = csfrm * 256 + ora_type_num
|
||||||
self.num = num
|
self.num = num
|
||||||
self.name = name
|
self.name = name
|
||||||
self.default_size = default_size
|
self.default_size = default_size
|
||||||
|
self._ora_name = ora_name
|
||||||
self._ora_type_num = ora_type_num
|
self._ora_type_num = ora_type_num
|
||||||
self._csfrm = csfrm
|
self._csfrm = csfrm
|
||||||
self._buffer_size_factor = buffer_size_factor
|
self._buffer_size_factor = buffer_size_factor
|
||||||
db_type_by_num[num] = self
|
db_type_by_num[num] = self
|
||||||
|
db_type_by_ora_name[ora_name] = self
|
||||||
db_type_by_ora_type_num[ora_type_key] = self
|
db_type_by_ora_type_num[ora_type_key] = self
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
|
@ -81,6 +84,14 @@ cdef class DbType:
|
||||||
pass
|
pass
|
||||||
errors._raise_err(errors.ERR_ORACLE_TYPE_NOT_SUPPORTED, num=num)
|
errors._raise_err(errors.ERR_ORACLE_TYPE_NOT_SUPPORTED, num=num)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef DbType _from_ora_name(str name):
|
||||||
|
try:
|
||||||
|
return db_type_by_ora_name[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
errors._raise_err(errors.ERR_ORACLE_TYPE_NAME_NOT_SUPPORTED, name=name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef DbType _from_ora_type_and_csfrm(uint8_t ora_type_num, uint8_t csfrm):
|
cdef DbType _from_ora_type_and_csfrm(uint8_t ora_type_num, uint8_t csfrm):
|
||||||
cdef uint16_t ora_type_key = csfrm * 256 + ora_type_num
|
cdef uint16_t ora_type_key = csfrm * 256 + ora_type_num
|
||||||
|
@ -93,62 +104,75 @@ cdef class DbType:
|
||||||
|
|
||||||
|
|
||||||
# database types
|
# database types
|
||||||
DB_TYPE_BFILE = DbType(DB_TYPE_NUM_BFILE, "DB_TYPE_BFILE", 114)
|
DB_TYPE_BFILE = DbType(DB_TYPE_NUM_BFILE, "DB_TYPE_BFILE", "BFILE", 114)
|
||||||
DB_TYPE_BINARY_DOUBLE = DbType(DB_TYPE_NUM_BINARY_DOUBLE,
|
DB_TYPE_BINARY_DOUBLE = DbType(DB_TYPE_NUM_BINARY_DOUBLE,
|
||||||
"DB_TYPE_BINARY_DOUBLE", 101,
|
"DB_TYPE_BINARY_DOUBLE", "BINARY_DOUBLE", 101,
|
||||||
buffer_size_factor=8)
|
buffer_size_factor=8)
|
||||||
DB_TYPE_BINARY_FLOAT = DbType(DB_TYPE_NUM_BINARY_FLOAT, "DB_TYPE_BINARY_FLOAT",
|
DB_TYPE_BINARY_FLOAT = DbType(DB_TYPE_NUM_BINARY_FLOAT, "DB_TYPE_BINARY_FLOAT",
|
||||||
100, buffer_size_factor=4)
|
"BINARY_FLOAT", 100, buffer_size_factor=4)
|
||||||
DB_TYPE_BINARY_INTEGER = DbType(DB_TYPE_NUM_BINARY_INTEGER,
|
DB_TYPE_BINARY_INTEGER = DbType(DB_TYPE_NUM_BINARY_INTEGER,
|
||||||
"DB_TYPE_BINARY_INTEGER", 3,
|
"DB_TYPE_BINARY_INTEGER", "BINARY_INTEGER", 3,
|
||||||
buffer_size_factor=22)
|
buffer_size_factor=22)
|
||||||
DB_TYPE_BLOB = DbType(DB_TYPE_NUM_BLOB, "DB_TYPE_BLOB", 113,
|
DB_TYPE_BLOB = DbType(DB_TYPE_NUM_BLOB, "DB_TYPE_BLOB", "BLOB", 113,
|
||||||
buffer_size_factor=112)
|
buffer_size_factor=112)
|
||||||
DB_TYPE_BOOLEAN = DbType(DB_TYPE_NUM_BOOLEAN, "DB_TYPE_BOOLEAN", 252,
|
DB_TYPE_BOOLEAN = DbType(DB_TYPE_NUM_BOOLEAN, "DB_TYPE_BOOLEAN", "BOOLEAN",
|
||||||
buffer_size_factor=4)
|
252, buffer_size_factor=4)
|
||||||
DB_TYPE_CHAR = DbType(DB_TYPE_NUM_CHAR, "DB_TYPE_CHAR", 96, 2000, csfrm=1,
|
DB_TYPE_CHAR = DbType(DB_TYPE_NUM_CHAR, "DB_TYPE_CHAR", "CHAR", 96, 2000,
|
||||||
buffer_size_factor=4)
|
csfrm=1, buffer_size_factor=4)
|
||||||
DB_TYPE_CLOB = DbType(DB_TYPE_NUM_CLOB, "DB_TYPE_CLOB", 112, csfrm=1,
|
DB_TYPE_CLOB = DbType(DB_TYPE_NUM_CLOB, "DB_TYPE_CLOB", "CLOB", 112, csfrm=1,
|
||||||
buffer_size_factor=112)
|
buffer_size_factor=112)
|
||||||
DB_TYPE_CURSOR = DbType(DB_TYPE_NUM_CURSOR, "DB_TYPE_CURSOR", 102,
|
DB_TYPE_CURSOR = DbType(DB_TYPE_NUM_CURSOR, "DB_TYPE_CURSOR", "CURSOR", 102,
|
||||||
buffer_size_factor=4)
|
buffer_size_factor=4)
|
||||||
DB_TYPE_DATE = DbType(DB_TYPE_NUM_DATE, "DB_TYPE_DATE", 12,
|
DB_TYPE_DATE = DbType(DB_TYPE_NUM_DATE, "DB_TYPE_DATE", "DATE", 12,
|
||||||
buffer_size_factor=7)
|
buffer_size_factor=7)
|
||||||
DB_TYPE_INTERVAL_DS = DbType(DB_TYPE_NUM_INTERVAL_DS, "DB_TYPE_INTERVAL_DS",
|
DB_TYPE_INTERVAL_DS = DbType(DB_TYPE_NUM_INTERVAL_DS, "DB_TYPE_INTERVAL_DS",
|
||||||
183, buffer_size_factor=11)
|
"INTERVAL DAY TO SECOND", 183,
|
||||||
|
buffer_size_factor=11)
|
||||||
DB_TYPE_INTERVAL_YM = DbType(DB_TYPE_NUM_INTERVAL_YM, "DB_TYPE_INTERVAL_YM",
|
DB_TYPE_INTERVAL_YM = DbType(DB_TYPE_NUM_INTERVAL_YM, "DB_TYPE_INTERVAL_YM",
|
||||||
182)
|
"INTERVAL YEAR TO MONTH", 182)
|
||||||
DB_TYPE_JSON = DbType(DB_TYPE_NUM_JSON, "DB_TYPE_JSON", 119)
|
DB_TYPE_JSON = DbType(DB_TYPE_NUM_JSON, "DB_TYPE_JSON", "JSON", 119)
|
||||||
DB_TYPE_LONG = DbType(DB_TYPE_NUM_LONG_VARCHAR, "DB_TYPE_LONG", 8, csfrm=1,
|
DB_TYPE_LONG = DbType(DB_TYPE_NUM_LONG_VARCHAR, "DB_TYPE_LONG", "LONG",
|
||||||
buffer_size_factor=2147483647)
|
8, csfrm=1, buffer_size_factor=2147483647)
|
||||||
DB_TYPE_LONG_NVARCHAR = DbType(DB_TYPE_NUM_LONG_NVARCHAR,
|
DB_TYPE_LONG_NVARCHAR = DbType(DB_TYPE_NUM_LONG_NVARCHAR,
|
||||||
"DB_TYPE_LONG_NVARCHAR", 8, csfrm=2,
|
"DB_TYPE_LONG_NVARCHAR", "LONG NVARCHAR", 8,
|
||||||
buffer_size_factor=2147483647)
|
csfrm=2, buffer_size_factor=2147483647)
|
||||||
DB_TYPE_LONG_RAW = DbType(DB_TYPE_NUM_LONG_RAW, "DB_TYPE_LONG_RAW", 24,
|
DB_TYPE_LONG_RAW = DbType(DB_TYPE_NUM_LONG_RAW, "DB_TYPE_LONG_RAW", "LONG RAW",
|
||||||
buffer_size_factor=2147483647)
|
24, buffer_size_factor=2147483647)
|
||||||
DB_TYPE_NCHAR = DbType(DB_TYPE_NUM_NCHAR, "DB_TYPE_NCHAR", 96, 2000, csfrm=2,
|
DB_TYPE_NCHAR = DbType(DB_TYPE_NUM_NCHAR, "DB_TYPE_NCHAR", "NCHAR", 96, 2000,
|
||||||
buffer_size_factor=4)
|
csfrm=2, buffer_size_factor=4)
|
||||||
DB_TYPE_NCLOB = DbType(DB_TYPE_NUM_NCLOB, "DB_TYPE_NCLOB", 112, csfrm=2,
|
DB_TYPE_NCLOB = DbType(DB_TYPE_NUM_NCLOB, "DB_TYPE_NCLOB", "NCLOB", 112,
|
||||||
buffer_size_factor=112)
|
csfrm=2, buffer_size_factor=112)
|
||||||
DB_TYPE_NUMBER = DbType(DB_TYPE_NUM_NUMBER, "DB_TYPE_NUMBER", 2,
|
DB_TYPE_NUMBER = DbType(DB_TYPE_NUM_NUMBER, "DB_TYPE_NUMBER", "NUMBER", 2,
|
||||||
buffer_size_factor=22)
|
buffer_size_factor=22)
|
||||||
DB_TYPE_NVARCHAR = DbType(DB_TYPE_NUM_NVARCHAR, "DB_TYPE_NVARCHAR", 1, 4000,
|
DB_TYPE_NVARCHAR = DbType(DB_TYPE_NUM_NVARCHAR, "DB_TYPE_NVARCHAR",
|
||||||
csfrm=2, buffer_size_factor=4)
|
"NVARCHAR2", 1, 4000, csfrm=2, buffer_size_factor=4)
|
||||||
DB_TYPE_OBJECT = DbType(DB_TYPE_NUM_OBJECT, "DB_TYPE_OBJECT", 109)
|
DB_TYPE_OBJECT = DbType(DB_TYPE_NUM_OBJECT, "DB_TYPE_OBJECT", "OBJECT", 109)
|
||||||
DB_TYPE_RAW = DbType(DB_TYPE_NUM_RAW, "DB_TYPE_RAW", 23, 4000,
|
DB_TYPE_RAW = DbType(DB_TYPE_NUM_RAW, "DB_TYPE_RAW", "RAW", 23, 4000,
|
||||||
buffer_size_factor=1)
|
buffer_size_factor=1)
|
||||||
DB_TYPE_ROWID = DbType(DB_TYPE_NUM_ROWID, "DB_TYPE_ROWID", 11,
|
DB_TYPE_ROWID = DbType(DB_TYPE_NUM_ROWID, "DB_TYPE_ROWID", "ROWID", 11,
|
||||||
buffer_size_factor=18)
|
buffer_size_factor=18)
|
||||||
DB_TYPE_TIMESTAMP = DbType(DB_TYPE_NUM_TIMESTAMP, "DB_TYPE_TIMESTAMP", 180,
|
DB_TYPE_TIMESTAMP = DbType(DB_TYPE_NUM_TIMESTAMP, "DB_TYPE_TIMESTAMP",
|
||||||
buffer_size_factor=11)
|
"TIMESTAMP", 180, buffer_size_factor=11)
|
||||||
DB_TYPE_TIMESTAMP_LTZ = DbType(DB_TYPE_NUM_TIMESTAMP_LTZ,
|
DB_TYPE_TIMESTAMP_LTZ = DbType(DB_TYPE_NUM_TIMESTAMP_LTZ,
|
||||||
"DB_TYPE_TIMESTAMP_LTZ", 231,
|
"DB_TYPE_TIMESTAMP_LTZ",
|
||||||
|
"TIMESTAMP WITH LOCAL TZ", 231,
|
||||||
buffer_size_factor=11)
|
buffer_size_factor=11)
|
||||||
DB_TYPE_TIMESTAMP_TZ = DbType(DB_TYPE_NUM_TIMESTAMP_TZ, "DB_TYPE_TIMESTAMP_TZ",
|
DB_TYPE_TIMESTAMP_TZ = DbType(DB_TYPE_NUM_TIMESTAMP_TZ, "DB_TYPE_TIMESTAMP_TZ",
|
||||||
181, buffer_size_factor=13)
|
"TIMESTAMP WITH TZ", 181,
|
||||||
DB_TYPE_UROWID = DbType(DB_TYPE_NUM_UROWID, "DB_TYPE_UROWID", 208)
|
buffer_size_factor=13)
|
||||||
DB_TYPE_VARCHAR = DbType(DB_TYPE_NUM_VARCHAR, "DB_TYPE_VARCHAR", 1, 4000,
|
DB_TYPE_UROWID = DbType(DB_TYPE_NUM_UROWID, "DB_TYPE_UROWID", "UROWID", 208)
|
||||||
csfrm=1, buffer_size_factor=4)
|
DB_TYPE_VARCHAR = DbType(DB_TYPE_NUM_VARCHAR, "DB_TYPE_VARCHAR", "VARCHAR2",
|
||||||
|
1, 4000, csfrm=1, buffer_size_factor=4)
|
||||||
|
|
||||||
|
# additional aliases
|
||||||
|
db_type_by_ora_name["DOUBLE PRECISION"] = DB_TYPE_NUMBER
|
||||||
|
db_type_by_ora_name["FLOAT"] = DB_TYPE_NUMBER
|
||||||
|
db_type_by_ora_name["INTEGER"] = DB_TYPE_NUMBER
|
||||||
|
db_type_by_ora_name["PL/SQL BOOLEAN"] = DB_TYPE_BOOLEAN
|
||||||
|
db_type_by_ora_name["PL/SQL BINARY INTEGER"] = DB_TYPE_BINARY_INTEGER
|
||||||
|
db_type_by_ora_name["PL/SQL PLS INTEGER"] = DB_TYPE_BINARY_INTEGER
|
||||||
|
db_type_by_ora_name["REAL"] = DB_TYPE_NUMBER
|
||||||
|
db_type_by_ora_name["SMALLINT"] = DB_TYPE_NUMBER
|
||||||
|
|
||||||
# DB API types
|
# DB API types
|
||||||
BINARY = ApiType("BINARY", DB_TYPE_RAW, DB_TYPE_LONG_RAW)
|
BINARY = ApiType("BINARY", DB_TYPE_RAW, DB_TYPE_LONG_RAW)
|
||||||
|
|
|
@ -49,115 +49,25 @@ cdef class BaseVarImpl:
|
||||||
exception is raised when the Python value is found to be unacceptable;
|
exception is raised when the Python value is found to be unacceptable;
|
||||||
otherwise, the flag is cleared if the Python value is unacceptable.
|
otherwise, the flag is cleared if the Python value is unacceptable.
|
||||||
"""
|
"""
|
||||||
cdef:
|
cdef uint32_t size
|
||||||
uint32_t db_type_num, size
|
|
||||||
bint type_ok = True
|
|
||||||
BaseLobImpl lob_impl
|
|
||||||
object orig_value
|
|
||||||
|
|
||||||
# call in converter, if applicable
|
# call in converter, if applicable
|
||||||
if self.inconverter is not None:
|
if self.inconverter is not None:
|
||||||
value = self.inconverter(value)
|
value = self.inconverter(value)
|
||||||
|
|
||||||
# if value is None, no further checks are needed
|
# check the value and verify it is acceptable
|
||||||
if value is None:
|
value = self._conn_impl._check_value(self.dbtype, self.objtype, value,
|
||||||
self._set_scalar_value(pos, value)
|
was_set)
|
||||||
self._is_value_set = True
|
if was_set != NULL and not was_set[0]:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# check to see that the type of value is accepted; perform any
|
# resize variable, if applicable
|
||||||
# necessary adjustments
|
if value is not None and self.dbtype.default_size != 0:
|
||||||
db_type_num = self.dbtype.num
|
size = <uint32_t> len(value)
|
||||||
if db_type_num in (DB_TYPE_NUM_NUMBER, DB_TYPE_NUM_BINARY_INTEGER,
|
if size > self.size:
|
||||||
DB_TYPE_NUM_BINARY_DOUBLE,
|
self._resize(size)
|
||||||
DB_TYPE_NUM_BINARY_FLOAT):
|
|
||||||
type_ok = isinstance(value,
|
# set value
|
||||||
(PY_TYPE_BOOL, int, float, PY_TYPE_DECIMAL))
|
|
||||||
if type_ok:
|
|
||||||
if db_type_num in (DB_TYPE_NUM_BINARY_FLOAT,
|
|
||||||
DB_TYPE_NUM_BINARY_DOUBLE):
|
|
||||||
value = float(value)
|
|
||||||
elif db_type_num == DB_TYPE_NUM_BINARY_INTEGER \
|
|
||||||
or cpython.PyBool_Check(value):
|
|
||||||
value = int(value)
|
|
||||||
elif db_type_num in (DB_TYPE_NUM_CHAR, DB_TYPE_NUM_VARCHAR,
|
|
||||||
DB_TYPE_NUM_NCHAR, DB_TYPE_NUM_NVARCHAR,
|
|
||||||
DB_TYPE_NUM_LONG_VARCHAR,
|
|
||||||
DB_TYPE_NUM_LONG_NVARCHAR):
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
value = (<bytes> value).decode()
|
|
||||||
else:
|
|
||||||
type_ok = isinstance(value, str)
|
|
||||||
if type_ok:
|
|
||||||
size = <uint32_t> len(<str> value)
|
|
||||||
if size > self.size:
|
|
||||||
self._resize(size)
|
|
||||||
elif db_type_num in (DB_TYPE_NUM_RAW, DB_TYPE_NUM_LONG_RAW):
|
|
||||||
if isinstance(value, str):
|
|
||||||
value = (<str> value).encode()
|
|
||||||
else:
|
|
||||||
type_ok = isinstance(value, bytes)
|
|
||||||
if type_ok:
|
|
||||||
size = <uint32_t> len(<bytes> value)
|
|
||||||
if size > self.size:
|
|
||||||
self._resize(size)
|
|
||||||
elif db_type_num in (DB_TYPE_NUM_DATE, DB_TYPE_NUM_TIMESTAMP,
|
|
||||||
DB_TYPE_NUM_TIMESTAMP_LTZ,
|
|
||||||
DB_TYPE_NUM_TIMESTAMP_TZ):
|
|
||||||
type_ok = cydatetime.PyDateTime_Check(value) \
|
|
||||||
or cydatetime.PyDate_Check(value)
|
|
||||||
elif db_type_num == DB_TYPE_NUM_INTERVAL_DS:
|
|
||||||
type_ok = isinstance(value, PY_TYPE_TIMEDELTA)
|
|
||||||
elif db_type_num in (DB_TYPE_NUM_CLOB,
|
|
||||||
DB_TYPE_NUM_NCLOB,
|
|
||||||
DB_TYPE_NUM_BLOB):
|
|
||||||
if isinstance(value, PY_TYPE_LOB):
|
|
||||||
lob_impl = value._impl
|
|
||||||
if lob_impl.dbtype is not self.dbtype:
|
|
||||||
if was_set != NULL:
|
|
||||||
was_set[0] = False
|
|
||||||
return 0
|
|
||||||
errors._raise_err(errors.ERR_LOB_OF_WRONG_TYPE,
|
|
||||||
actual_type_name=lob_impl.dbtype.name,
|
|
||||||
expected_type_name=self.dbtype.name)
|
|
||||||
else:
|
|
||||||
type_ok = isinstance(value, (bytes, str))
|
|
||||||
if type_ok:
|
|
||||||
orig_value = self._get_scalar_value(pos)
|
|
||||||
if isinstance(orig_value, PY_TYPE_LOB):
|
|
||||||
orig_value.trim()
|
|
||||||
if value:
|
|
||||||
orig_value.write(value)
|
|
||||||
value = orig_value
|
|
||||||
elif db_type_num == DB_TYPE_NUM_OBJECT:
|
|
||||||
type_ok = isinstance(value, PY_TYPE_DB_OBJECT)
|
|
||||||
if type_ok and value._impl.type != self.objtype:
|
|
||||||
if was_set != NULL:
|
|
||||||
was_set[0] = False
|
|
||||||
return 0
|
|
||||||
errors._raise_err(errors.ERR_WRONG_OBJECT_TYPE,
|
|
||||||
actual_schema=value.type.schema,
|
|
||||||
actual_name=value.type.name,
|
|
||||||
expected_schema=self.objtype.schema,
|
|
||||||
expected_name=self.objtype.name)
|
|
||||||
elif db_type_num == DB_TYPE_NUM_CURSOR:
|
|
||||||
type_ok = isinstance(value, PY_TYPE_CURSOR)
|
|
||||||
elif db_type_num in (DB_TYPE_NUM_ROWID, DB_TYPE_NUM_UROWID,
|
|
||||||
DB_TYPE_NUM_INTERVAL_YM):
|
|
||||||
if was_set != NULL:
|
|
||||||
was_set[0] = False
|
|
||||||
return 0
|
|
||||||
errors._raise_err(errors.ERR_UNSUPPORTED_VAR_SET,
|
|
||||||
db_type_name=self.dbtype.name)
|
|
||||||
elif db_type_num == DB_TYPE_NUM_BOOLEAN:
|
|
||||||
value = bool(value)
|
|
||||||
if not type_ok:
|
|
||||||
if was_set != NULL:
|
|
||||||
was_set[0] = False
|
|
||||||
return 0
|
|
||||||
errors._raise_err(errors.ERR_UNSUPPORTED_PYTHON_TYPE_FOR_VAR,
|
|
||||||
py_type_name = type(value).__name__,
|
|
||||||
db_type_name = self.dbtype.name)
|
|
||||||
self._set_scalar_value(pos, value)
|
self._set_scalar_value(pos, value)
|
||||||
self._is_value_set = True
|
self._is_value_set = True
|
||||||
|
|
||||||
|
|
|
@ -507,7 +507,7 @@ cdef class ThickConnImpl(BaseConnImpl):
|
||||||
_raise_from_odpi()
|
_raise_from_odpi()
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_type(self, str name):
|
def get_type(self, object conn, str name):
|
||||||
cdef:
|
cdef:
|
||||||
dpiObjectType *handle
|
dpiObjectType *handle
|
||||||
const char *name_ptr
|
const char *name_ptr
|
||||||
|
|
|
@ -50,7 +50,7 @@ cdef class ThickDbObjectImpl(BaseDbObjectImpl):
|
||||||
data.isNull = 0
|
data.isNull = 0
|
||||||
_convert_from_python(value, dbtype, objtype, &data.value, buf)
|
_convert_from_python(value, dbtype, objtype, &data.value, buf)
|
||||||
|
|
||||||
def append(self, object value):
|
def append_checked(self, object value):
|
||||||
"""
|
"""
|
||||||
Internal method for appending a value to a collection object.
|
Internal method for appending a value to a collection object.
|
||||||
"""
|
"""
|
||||||
|
@ -204,7 +204,7 @@ cdef class ThickDbObjectImpl(BaseDbObjectImpl):
|
||||||
_raise_from_odpi()
|
_raise_from_odpi()
|
||||||
return size
|
return size
|
||||||
|
|
||||||
def set_attr_value(self, ThickDbObjectAttrImpl attr, object value):
|
def set_attr_value_checked(self, ThickDbObjectAttrImpl attr, object value):
|
||||||
"""
|
"""
|
||||||
Internal method for setting an attribute value.
|
Internal method for setting an attribute value.
|
||||||
"""
|
"""
|
||||||
|
@ -222,7 +222,7 @@ cdef class ThickDbObjectImpl(BaseDbObjectImpl):
|
||||||
native_type_num, &data) < 0:
|
native_type_num, &data) < 0:
|
||||||
_raise_from_odpi()
|
_raise_from_odpi()
|
||||||
|
|
||||||
def set_element_by_index(self, int32_t index, object value):
|
def set_element_by_index_checked(self, int32_t index, object value):
|
||||||
"""
|
"""
|
||||||
Internal method for setting an entry in a collection that is indexed by
|
Internal method for setting an entry in a collection that is indexed by
|
||||||
integers.
|
integers.
|
||||||
|
|
|
@ -37,7 +37,6 @@ cdef class ThickVarImpl(BaseVarImpl):
|
||||||
uint32_t _native_type_num
|
uint32_t _native_type_num
|
||||||
bint _get_returned_data
|
bint _get_returned_data
|
||||||
object _conn
|
object _conn
|
||||||
ThickConnImpl _conn_impl
|
|
||||||
|
|
||||||
def __dealloc__(self):
|
def __dealloc__(self):
|
||||||
if self._handle != NULL:
|
if self._handle != NULL:
|
||||||
|
@ -75,6 +74,7 @@ cdef class ThickVarImpl(BaseVarImpl):
|
||||||
|
|
||||||
cdef int _create_handle(self) except -1:
|
cdef int _create_handle(self) except -1:
|
||||||
cdef:
|
cdef:
|
||||||
|
ThickConnImpl conn_impl = self._conn_impl
|
||||||
dpiObjectType *obj_type_handle = NULL
|
dpiObjectType *obj_type_handle = NULL
|
||||||
ThickDbObjectTypeImpl obj_type_impl
|
ThickDbObjectTypeImpl obj_type_impl
|
||||||
if self._handle != NULL:
|
if self._handle != NULL:
|
||||||
|
@ -84,7 +84,7 @@ cdef class ThickVarImpl(BaseVarImpl):
|
||||||
obj_type_impl = <ThickDbObjectTypeImpl> self.objtype
|
obj_type_impl = <ThickDbObjectTypeImpl> self.objtype
|
||||||
obj_type_handle = obj_type_impl._handle
|
obj_type_handle = obj_type_impl._handle
|
||||||
self._native_type_num = _get_native_type_num(self.dbtype)
|
self._native_type_num = _get_native_type_num(self.dbtype)
|
||||||
if dpiConn_newVar(self._conn_impl._handle, self.dbtype.num,
|
if dpiConn_newVar(conn_impl._handle, self.dbtype.num,
|
||||||
self._native_type_num, self.num_elements, self.size,
|
self._native_type_num, self.num_elements, self.size,
|
||||||
0, self.is_array, obj_type_handle, &self._handle,
|
0, self.is_array, obj_type_handle, &self._handle,
|
||||||
&self._data) < 0:
|
&self._data) < 0:
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -65,6 +65,7 @@ cdef class ThinConnImpl(BaseConnImpl):
|
||||||
uint32_t _temp_lobs_total_size
|
uint32_t _temp_lobs_total_size
|
||||||
uint32_t _call_timeout
|
uint32_t _call_timeout
|
||||||
str _cclass
|
str _cclass
|
||||||
|
int _dbobject_type_cache_num
|
||||||
|
|
||||||
def __init__(self, str dsn, ConnectParamsImpl params):
|
def __init__(self, str dsn, ConnectParamsImpl params):
|
||||||
if not HAS_CRYPTOGRAPHY:
|
if not HAS_CRYPTOGRAPHY:
|
||||||
|
@ -284,6 +285,9 @@ cdef class ThinConnImpl(BaseConnImpl):
|
||||||
|
|
||||||
def close(self, bint in_del=False):
|
def close(self, bint in_del=False):
|
||||||
try:
|
try:
|
||||||
|
if self._dbobject_type_cache_num > 0:
|
||||||
|
remove_dbobject_type_cache(self._dbobject_type_cache_num)
|
||||||
|
self._dbobject_type_cache_num = 0
|
||||||
self._protocol._close(self)
|
self._protocol._close(self)
|
||||||
except (ssl.SSLError, exceptions.DatabaseError):
|
except (ssl.SSLError, exceptions.DatabaseError):
|
||||||
pass
|
pass
|
||||||
|
@ -299,6 +303,7 @@ cdef class ThinConnImpl(BaseConnImpl):
|
||||||
self._statement_cache = collections.OrderedDict()
|
self._statement_cache = collections.OrderedDict()
|
||||||
self._statement_cache_size = params.stmtcachesize
|
self._statement_cache_size = params.stmtcachesize
|
||||||
self._statement_cache_lock = threading.Lock()
|
self._statement_cache_lock = threading.Lock()
|
||||||
|
self._dbobject_type_cache_num = create_new_dbobject_type_cache(self)
|
||||||
self._cursors_to_close = array.array('I')
|
self._cursors_to_close = array.array('I')
|
||||||
array.resize(self._cursors_to_close, TNS_MAX_CURSORS_TO_CLOSE)
|
array.resize(self._cursors_to_close, TNS_MAX_CURSORS_TO_CLOSE)
|
||||||
self.invoke_session_callback = True
|
self.invoke_session_callback = True
|
||||||
|
@ -334,6 +339,11 @@ cdef class ThinConnImpl(BaseConnImpl):
|
||||||
def get_stmt_cache_size(self):
|
def get_stmt_cache_size(self):
|
||||||
return self._statement_cache_size
|
return self._statement_cache_size
|
||||||
|
|
||||||
|
def get_type(self, object conn, str name):
|
||||||
|
cdef ThinDbObjectTypeCache cache = \
|
||||||
|
get_dbobject_type_cache(self._dbobject_type_cache_num)
|
||||||
|
return cache.get_type(conn, name)
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
return self._server_version
|
return self._server_version
|
||||||
|
|
||||||
|
|
|
@ -413,6 +413,44 @@ DEF TNS_BIND_DIR_OUTPUT = 16
|
||||||
DEF TNS_BIND_DIR_INPUT = 32
|
DEF TNS_BIND_DIR_INPUT = 32
|
||||||
DEF TNS_BIND_DIR_INPUT_OUTPUT = 48
|
DEF TNS_BIND_DIR_INPUT_OUTPUT = 48
|
||||||
|
|
||||||
|
# database object image flags
|
||||||
|
DEF TNS_OBJ_IS_VERSION_81 = 0x80
|
||||||
|
DEF TNS_OBJ_IS_DEGENERATE = 0x10
|
||||||
|
DEF TNS_OBJ_IS_COLLECTION = 0x08
|
||||||
|
DEF TNS_OBJ_NO_PREFIX_SEG = 0x04
|
||||||
|
DEF TNS_OBJ_IMAGE_VERSION = 1
|
||||||
|
|
||||||
|
# database object flags
|
||||||
|
DEF TNS_OBJ_MAX_LENGTH = 245
|
||||||
|
DEF TNS_OBJ_ATOMIC_NULL = 253
|
||||||
|
DEF TNS_OBJ_NON_NULL_OID = 0x02
|
||||||
|
DEF TNS_OBJ_HAS_EXTENT_OID = 0x08
|
||||||
|
DEF TNS_OBJ_TOP_LEVEL = 0x01
|
||||||
|
DEF TNS_OBJ_HAS_INDEXES = 0x10
|
||||||
|
|
||||||
|
# database object collection types
|
||||||
|
DEF TNS_OBJ_PLSQL_INDEX_TABLE = 1
|
||||||
|
DEF TNS_OBJ_NESTED_TABLE = 2
|
||||||
|
DEF TNS_OBJ_VARRAY = 3
|
||||||
|
|
||||||
|
# database object TDS type codes
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_CHAR = 1
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_DATE = 2
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_FLOAT = 5
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_NUMBER = 6
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_VARCHAR = 7
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_BOOLEAN = 8
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_RAW = 19
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_TIMESTAMP = 21
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_TIMESTAMP_TZ = 23
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_OBJ = 27
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_COLL = 28
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_CLOB = 29
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_BLOB = 30
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_TIMESTAMP_LTZ = 33
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_BINARY_FLOAT = 37
|
||||||
|
DEF TNS_OBJ_TDS_TYPE_BINARY_DOUBLE = 45
|
||||||
|
|
||||||
# execute options
|
# execute options
|
||||||
DEF TNS_EXEC_OPTION_PARSE = 0x01
|
DEF TNS_EXEC_OPTION_PARSE = 0x01
|
||||||
DEF TNS_EXEC_OPTION_BIND = 0x08
|
DEF TNS_EXEC_OPTION_BIND = 0x08
|
||||||
|
@ -627,6 +665,7 @@ DEF TNS_BASE64_ALPHABET = \
|
||||||
b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
cdef bytearray TNS_BASE64_ALPHABET_ARRAY = \
|
cdef bytearray TNS_BASE64_ALPHABET_ARRAY = \
|
||||||
bytearray(TNS_BASE64_ALPHABET)
|
bytearray(TNS_BASE64_ALPHABET)
|
||||||
|
cdef bytes TNS_EXTENT_OID = bytes.fromhex('00000000000000000000000000010001')
|
||||||
|
|
||||||
# purity types
|
# purity types
|
||||||
DEF PURITY_DEFAULT = 0
|
DEF PURITY_DEFAULT = 0
|
||||||
|
|
|
@ -50,7 +50,10 @@ cdef class ThinCursorImpl(BaseCursorImpl):
|
||||||
self._statement = None
|
self._statement = None
|
||||||
|
|
||||||
cdef BaseVarImpl _create_var_impl(self, object conn):
|
cdef BaseVarImpl _create_var_impl(self, object conn):
|
||||||
return ThinVarImpl.__new__(ThinVarImpl)
|
cdef ThinVarImpl var_impl
|
||||||
|
var_impl = ThinVarImpl.__new__(ThinVarImpl)
|
||||||
|
var_impl._conn_impl = self._conn_impl
|
||||||
|
return var_impl
|
||||||
|
|
||||||
cdef MessageWithData _create_message(self, type typ, object cursor):
|
cdef MessageWithData _create_message(self, type typ, object cursor):
|
||||||
"""
|
"""
|
||||||
|
@ -114,14 +117,18 @@ cdef class ThinCursorImpl(BaseCursorImpl):
|
||||||
name=bind_info._bind_name)
|
name=bind_info._bind_name)
|
||||||
|
|
||||||
def execute(self, cursor):
|
def execute(self, cursor):
|
||||||
cdef MessageWithData message
|
cdef:
|
||||||
self._preprocess_execute(cursor.connection)
|
object conn = cursor.connection
|
||||||
|
MessageWithData message
|
||||||
|
self._preprocess_execute(conn)
|
||||||
message = self._create_message(ExecuteMessage, cursor)
|
message = self._create_message(ExecuteMessage, cursor)
|
||||||
message.num_execs = 1
|
message.num_execs = 1
|
||||||
self._conn_impl._protocol._process_single_message(message)
|
self._conn_impl._protocol._process_single_message(message)
|
||||||
self._statement._requires_full_execute = False
|
self._statement._requires_full_execute = False
|
||||||
if self._statement._is_query:
|
if self._statement._is_query:
|
||||||
self.rowcount = 0
|
self.rowcount = 0
|
||||||
|
if message.type_cache is not None:
|
||||||
|
message.type_cache.populate_partial_types(conn)
|
||||||
|
|
||||||
def executemany(self, cursor, num_execs, batcherrors, arraydmlrowcounts):
|
def executemany(self, cursor, num_execs, batcherrors, arraydmlrowcounts):
|
||||||
cdef:
|
cdef:
|
||||||
|
|
|
@ -0,0 +1,968 @@
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2022, Oracle and/or its affiliates.
|
||||||
|
#
|
||||||
|
# This software is dual-licensed to you under the Universal Permissive License
|
||||||
|
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||||
|
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||||
|
# either license.
|
||||||
|
#
|
||||||
|
# If you elect to accept the software under the Apache License, Version 2.0,
|
||||||
|
# the following applies:
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# dbobject.pyx
|
||||||
|
#
|
||||||
|
# Cython file defining the thin DbObjectType, DbObjectAttr and DbObject
|
||||||
|
# implementation classes (embedded in thin_impl.pyx).
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@cython.final
|
||||||
|
cdef class DbObjectPickleBuffer(Buffer):
|
||||||
|
|
||||||
|
cdef int _read_raw_bytes_and_length(self, const char_type **ptr,
|
||||||
|
ssize_t *num_bytes) except -1:
|
||||||
|
"""
|
||||||
|
Helper function that processes the length (if needed) and then acquires
|
||||||
|
the specified number of bytes from the buffer.
|
||||||
|
"""
|
||||||
|
cdef uint32_t extended_num_bytes
|
||||||
|
if num_bytes[0] == TNS_LONG_LENGTH_INDICATOR:
|
||||||
|
self.read_uint32(&extended_num_bytes)
|
||||||
|
num_bytes[0] = <ssize_t> extended_num_bytes
|
||||||
|
ptr[0] = self._get_raw(num_bytes[0])
|
||||||
|
|
||||||
|
cdef int _write_raw_bytes_and_length(self, const char_type *ptr,
|
||||||
|
ssize_t num_bytes) except -1:
|
||||||
|
"""
|
||||||
|
Helper function that writes the length in the format required before
|
||||||
|
writing the bytes.
|
||||||
|
"""
|
||||||
|
self.write_length(num_bytes)
|
||||||
|
self.write_raw(ptr, <uint32_t> num_bytes)
|
||||||
|
|
||||||
|
cdef int get_is_atomic_null(self, bint* is_null) except -1:
|
||||||
|
"""
|
||||||
|
Reads the next byte and checks to see if the value is atomically null.
|
||||||
|
If not, the byte is returned to the buffer for further processing.
|
||||||
|
"""
|
||||||
|
cdef uint8_t value
|
||||||
|
self.read_ub1(&value)
|
||||||
|
if value in (TNS_OBJ_ATOMIC_NULL, TNS_NULL_LENGTH_INDICATOR):
|
||||||
|
is_null[0] = True
|
||||||
|
else:
|
||||||
|
is_null[0] = False
|
||||||
|
self._pos -= 1
|
||||||
|
|
||||||
|
cdef int read_header(self, uint8_t* flags, uint8_t *version) except -1:
|
||||||
|
"""
|
||||||
|
Reads the header of the pickled data.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint32_t prefix_seg_length
|
||||||
|
uint8_t tmp
|
||||||
|
self.read_ub1(flags)
|
||||||
|
self.read_ub1(version)
|
||||||
|
self.skip_length()
|
||||||
|
if flags[0] & TNS_OBJ_NO_PREFIX_SEG:
|
||||||
|
return 0
|
||||||
|
self.read_length(&prefix_seg_length)
|
||||||
|
self.skip_raw_bytes(prefix_seg_length)
|
||||||
|
|
||||||
|
cdef int read_length(self, uint32_t *length) except -1:
|
||||||
|
"""
|
||||||
|
Read the length from the buffer. This will be a single byte, unless the
|
||||||
|
value meets or exceeds TNS_LONG_LENGTH_INDICATOR. In that case, the
|
||||||
|
value is stored as a 4-byte integer.
|
||||||
|
"""
|
||||||
|
cdef uint8_t short_length
|
||||||
|
self.read_ub1(&short_length)
|
||||||
|
if short_length == TNS_LONG_LENGTH_INDICATOR:
|
||||||
|
self.read_uint32(length)
|
||||||
|
else:
|
||||||
|
length[0] = short_length
|
||||||
|
|
||||||
|
cdef int skip_length(self) except -1:
|
||||||
|
"""
|
||||||
|
Skips the length instead of reading it from the buffer.
|
||||||
|
"""
|
||||||
|
cdef uint8_t short_length
|
||||||
|
self.read_ub1(&short_length)
|
||||||
|
if short_length == TNS_LONG_LENGTH_INDICATOR:
|
||||||
|
self.skip_raw_bytes(4)
|
||||||
|
|
||||||
|
cdef int write_header(self, ThinDbObjectImpl obj_impl) except -1:
|
||||||
|
"""
|
||||||
|
Writes the header of the pickled data. Since the size is unknown at
|
||||||
|
this point, zero is written initially and the actual size is written
|
||||||
|
later.
|
||||||
|
"""
|
||||||
|
cdef ThinDbObjectTypeImpl typ_impl = obj_impl.type
|
||||||
|
self.write_uint8(obj_impl.image_flags)
|
||||||
|
self.write_uint8(obj_impl.image_version)
|
||||||
|
self.write_uint8(TNS_LONG_LENGTH_INDICATOR)
|
||||||
|
self.write_uint32(0)
|
||||||
|
if typ_impl.is_collection:
|
||||||
|
self.write_uint8(1) # length of prefix segment
|
||||||
|
self.write_uint8(1) # prefix segment contents
|
||||||
|
|
||||||
|
cdef int write_length(self, ssize_t length) except -1:
|
||||||
|
"""
|
||||||
|
Writes the length to the buffer.
|
||||||
|
"""
|
||||||
|
if length <= TNS_MAX_SHORT_LENGTH:
|
||||||
|
self.write_uint8(<uint8_t> length)
|
||||||
|
else:
|
||||||
|
self.write_uint8(TNS_LONG_LENGTH_INDICATOR)
|
||||||
|
self.write_uint32(<uint32_t> length)
|
||||||
|
|
||||||
|
|
||||||
|
@cython.final
|
||||||
|
cdef class TDSBuffer(Buffer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
cdef class ThinDbObjectImpl(BaseDbObjectImpl):
|
||||||
|
cdef:
|
||||||
|
uint8_t image_flags, image_version
|
||||||
|
bytes toid, oid, packed_data
|
||||||
|
uint32_t num_elements
|
||||||
|
dict unpacked_assoc_array
|
||||||
|
list unpacked_assoc_keys
|
||||||
|
dict unpacked_attrs
|
||||||
|
list unpacked_array
|
||||||
|
uint16_t flags
|
||||||
|
|
||||||
|
cdef inline int _ensure_assoc_keys(self) except -1:
|
||||||
|
"""
|
||||||
|
Ensure that the keys for the associative array have been calculated.
|
||||||
|
PL/SQL associative arrays keep their keys in sorted order so this must
|
||||||
|
be calculated when indices are required.
|
||||||
|
"""
|
||||||
|
if self.unpacked_assoc_keys is None:
|
||||||
|
self.unpacked_assoc_keys = list(sorted(self.unpacked_assoc_array))
|
||||||
|
|
||||||
|
cdef inline int _ensure_unpacked(self) except -1:
|
||||||
|
"""
|
||||||
|
Ensure that the data has been unpacked.
|
||||||
|
"""
|
||||||
|
if self.packed_data is not None:
|
||||||
|
self._unpack_data()
|
||||||
|
|
||||||
|
cdef bytes _get_packed_data(self):
|
||||||
|
"""
|
||||||
|
Returns the packed data for the object. This will either be the value
|
||||||
|
retrieved from the database or generated packed data (for new objects
|
||||||
|
and those that have had their data unpacked already).
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ThinDbObjectTypeImpl typ_impl = self.type
|
||||||
|
DbObjectPickleBuffer buf
|
||||||
|
ssize_t size
|
||||||
|
if self.packed_data is not None:
|
||||||
|
return self.packed_data
|
||||||
|
buf = DbObjectPickleBuffer.__new__(DbObjectPickleBuffer)
|
||||||
|
buf._initialize(TNS_CHUNK_SIZE)
|
||||||
|
buf.write_header(self)
|
||||||
|
self._pack_data(buf)
|
||||||
|
size = buf._pos
|
||||||
|
buf.skip_to(3)
|
||||||
|
buf.write_uint32(size)
|
||||||
|
return buf._data[:size]
|
||||||
|
|
||||||
|
cdef int _pack_data(self, DbObjectPickleBuffer buf) except -1:
|
||||||
|
"""
|
||||||
|
Packs the data from the object into the buffer.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ThinDbObjectTypeImpl typ_impl = self.type
|
||||||
|
ThinDbObjectAttrImpl attr
|
||||||
|
int32_t index
|
||||||
|
object value
|
||||||
|
if typ_impl.is_collection:
|
||||||
|
buf.write_uint8(typ_impl.collection_flags)
|
||||||
|
if typ_impl.collection_type == TNS_OBJ_PLSQL_INDEX_TABLE:
|
||||||
|
self._ensure_assoc_keys()
|
||||||
|
buf.write_length(len(self.unpacked_assoc_keys))
|
||||||
|
for index in self.unpacked_assoc_keys:
|
||||||
|
buf.write_uint32(<uint32_t> index)
|
||||||
|
self._pack_value(buf, typ_impl.element_dbtype,
|
||||||
|
typ_impl.element_objtype,
|
||||||
|
self.unpacked_assoc_array[index])
|
||||||
|
else:
|
||||||
|
buf.write_length(len(self.unpacked_array))
|
||||||
|
for value in self.unpacked_array:
|
||||||
|
self._pack_value(buf, typ_impl.element_dbtype,
|
||||||
|
typ_impl.element_objtype, value)
|
||||||
|
else:
|
||||||
|
for attr in typ_impl.attrs:
|
||||||
|
self._pack_value(buf, attr.dbtype, attr.objtype,
|
||||||
|
self.unpacked_attrs[attr.name])
|
||||||
|
|
||||||
|
cdef int _pack_value(self, DbObjectPickleBuffer buf,
|
||||||
|
DbType dbtype, ThinDbObjectTypeImpl objtype,
|
||||||
|
object value) except -1:
|
||||||
|
"""
|
||||||
|
Packs a value into the buffer. At this point it is assumed that the
|
||||||
|
value matches the correct type.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint8_t ora_type_num = dbtype._ora_type_num
|
||||||
|
ThinDbObjectImpl obj_impl
|
||||||
|
bytes temp_bytes
|
||||||
|
if value is None:
|
||||||
|
if objtype is not None and not objtype.is_collection:
|
||||||
|
buf.write_uint8(TNS_OBJ_ATOMIC_NULL)
|
||||||
|
else:
|
||||||
|
buf.write_uint8(TNS_NULL_LENGTH_INDICATOR)
|
||||||
|
elif ora_type_num in (TNS_DATA_TYPE_CHAR, TNS_DATA_TYPE_VARCHAR):
|
||||||
|
if dbtype._csfrm == TNS_CS_IMPLICIT:
|
||||||
|
temp_bytes = (<str> value).encode()
|
||||||
|
else:
|
||||||
|
temp_bytes = (<str> value).encode(TNS_ENCODING_UTF16)
|
||||||
|
buf.write_bytes_with_length(temp_bytes)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_NUMBER:
|
||||||
|
temp_bytes = (<str> cpython.PyObject_Str(value)).encode()
|
||||||
|
buf.write_oracle_number(temp_bytes)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BINARY_INTEGER:
|
||||||
|
buf.write_uint8(4)
|
||||||
|
buf.write_uint32(<uint32_t> value)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_RAW:
|
||||||
|
buf.write_bytes_with_length(value)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BINARY_DOUBLE:
|
||||||
|
buf.write_binary_double(value)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BINARY_FLOAT:
|
||||||
|
buf.write_binary_float(value)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BOOLEAN:
|
||||||
|
buf.write_uint8(4)
|
||||||
|
buf.write_uint32(value)
|
||||||
|
elif ora_type_num in (TNS_DATA_TYPE_DATE, TNS_DATA_TYPE_TIMESTAMP,
|
||||||
|
TNS_DATA_TYPE_TIMESTAMP_TZ,
|
||||||
|
TNS_DATA_TYPE_TIMESTAMP_LTZ):
|
||||||
|
buf.write_oracle_date(value, dbtype._buffer_size_factor)
|
||||||
|
elif ora_type_num in (TNS_DATA_TYPE_CLOB, TNS_DATA_TYPE_BLOB):
|
||||||
|
buf.write_lob(value._impl)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_INT_NAMED:
|
||||||
|
obj_impl = value._impl
|
||||||
|
if self.type.is_collection or obj_impl.type.is_collection:
|
||||||
|
temp_bytes = obj_impl._get_packed_data()
|
||||||
|
buf.write_bytes_with_length(temp_bytes)
|
||||||
|
else:
|
||||||
|
obj_impl._pack_data(buf)
|
||||||
|
else:
|
||||||
|
errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED,
|
||||||
|
name=dbtype.name)
|
||||||
|
|
||||||
|
cdef int _unpack_data(self) except -1:
|
||||||
|
"""
|
||||||
|
Unpacks the packed data into a dictionary of Python values.
|
||||||
|
"""
|
||||||
|
cdef DbObjectPickleBuffer buf
|
||||||
|
buf = DbObjectPickleBuffer.__new__(DbObjectPickleBuffer)
|
||||||
|
buf._populate_from_bytes(self.packed_data)
|
||||||
|
buf.read_header(&self.image_flags, &self.image_version)
|
||||||
|
self._unpack_data_from_buf(buf)
|
||||||
|
self.packed_data = None
|
||||||
|
|
||||||
|
cdef int _unpack_data_from_buf(self, DbObjectPickleBuffer buf) except -1:
|
||||||
|
"""
|
||||||
|
Unpacks the data from the buffer into Python values.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
dict unpacked_attrs = {}, unpacked_assoc_array = None
|
||||||
|
ThinDbObjectTypeImpl typ_impl = self.type
|
||||||
|
list unpacked_array = None
|
||||||
|
ThinDbObjectAttrImpl attr
|
||||||
|
uint32_t num_elements, i
|
||||||
|
int32_t assoc_index
|
||||||
|
object value
|
||||||
|
if typ_impl.is_collection:
|
||||||
|
if typ_impl.collection_type == TNS_OBJ_PLSQL_INDEX_TABLE:
|
||||||
|
unpacked_assoc_array = {}
|
||||||
|
else:
|
||||||
|
unpacked_array = []
|
||||||
|
buf.skip_raw_bytes(1) # collection flags
|
||||||
|
buf.read_length(&num_elements)
|
||||||
|
for i in range(num_elements):
|
||||||
|
if typ_impl.collection_type == TNS_OBJ_PLSQL_INDEX_TABLE:
|
||||||
|
buf.read_int32(&assoc_index)
|
||||||
|
value = self._unpack_value(buf, typ_impl.element_dbtype,
|
||||||
|
typ_impl.element_objtype)
|
||||||
|
if typ_impl.collection_type == TNS_OBJ_PLSQL_INDEX_TABLE:
|
||||||
|
unpacked_assoc_array[assoc_index] = value
|
||||||
|
else:
|
||||||
|
unpacked_array.append(value)
|
||||||
|
else:
|
||||||
|
unpacked_attrs = {}
|
||||||
|
for attr in typ_impl.attrs:
|
||||||
|
value = self._unpack_value(buf, attr.dbtype, attr.objtype)
|
||||||
|
unpacked_attrs[attr.name] = value
|
||||||
|
self.unpacked_attrs = unpacked_attrs
|
||||||
|
self.unpacked_array = unpacked_array
|
||||||
|
self.unpacked_assoc_array = unpacked_assoc_array
|
||||||
|
|
||||||
|
cdef object _unpack_value(self, DbObjectPickleBuffer buf,
|
||||||
|
DbType dbtype, ThinDbObjectTypeImpl objtype):
|
||||||
|
"""
|
||||||
|
Unpacks a single value and returns it.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint8_t ora_type_num = dbtype._ora_type_num
|
||||||
|
uint8_t csfrm = dbtype._csfrm
|
||||||
|
ThinDbObjectImpl obj_impl
|
||||||
|
ThinConnImpl conn_impl
|
||||||
|
bint is_null
|
||||||
|
if ora_type_num == TNS_DATA_TYPE_NUMBER:
|
||||||
|
return buf.read_oracle_number(NUM_TYPE_FLOAT)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BINARY_INTEGER:
|
||||||
|
return buf.read_binary_integer()
|
||||||
|
elif ora_type_num in (TNS_DATA_TYPE_VARCHAR, TNS_DATA_TYPE_CHAR):
|
||||||
|
if csfrm == TNS_CS_NCHAR:
|
||||||
|
conn_impl = self.type._conn_impl
|
||||||
|
conn_impl._protocol._caps._check_ncharset_id()
|
||||||
|
return buf.read_str(csfrm)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_RAW:
|
||||||
|
return buf.read_bytes()
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BINARY_DOUBLE:
|
||||||
|
return buf.read_binary_double()
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BINARY_FLOAT:
|
||||||
|
return buf.read_binary_float()
|
||||||
|
elif ora_type_num in (TNS_DATA_TYPE_DATE, TNS_DATA_TYPE_TIMESTAMP,
|
||||||
|
TNS_DATA_TYPE_TIMESTAMP_LTZ,
|
||||||
|
TNS_DATA_TYPE_TIMESTAMP_TZ):
|
||||||
|
return buf.read_date()
|
||||||
|
elif ora_type_num in (TNS_DATA_TYPE_CLOB, TNS_DATA_TYPE_BLOB):
|
||||||
|
conn_impl = self.type._conn_impl
|
||||||
|
return buf.read_lob(conn_impl, dbtype)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_BOOLEAN:
|
||||||
|
return buf.read_bool()
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_INT_NAMED:
|
||||||
|
buf.get_is_atomic_null(&is_null)
|
||||||
|
if is_null:
|
||||||
|
return None
|
||||||
|
obj_impl = ThinDbObjectImpl.__new__(ThinDbObjectImpl)
|
||||||
|
obj_impl.type = objtype
|
||||||
|
if objtype.is_collection or self.type.is_collection:
|
||||||
|
obj_impl.packed_data = buf.read_bytes()
|
||||||
|
else:
|
||||||
|
obj_impl._unpack_data_from_buf(buf)
|
||||||
|
return PY_TYPE_DB_OBJECT._from_impl(obj_impl)
|
||||||
|
errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED, name=dbtype.name)
|
||||||
|
|
||||||
|
def append_checked(self, object value):
|
||||||
|
"""
|
||||||
|
Internal method for appending a value to a collection object.
|
||||||
|
"""
|
||||||
|
cdef int32_t new_index
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array is not None:
|
||||||
|
self.unpacked_array.append(value)
|
||||||
|
else:
|
||||||
|
self._ensure_assoc_keys()
|
||||||
|
new_index = self.unpacked_assoc_keys[-1] + 1 \
|
||||||
|
if self.unpacked_assoc_keys else 0
|
||||||
|
self.unpacked_assoc_array[new_index] = value
|
||||||
|
self.unpacked_assoc_keys.append(new_index)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""
|
||||||
|
Internal method for creating a copy of an object.
|
||||||
|
"""
|
||||||
|
cdef ThinDbObjectImpl copied_impl
|
||||||
|
copied_impl = ThinDbObjectImpl.__new__(ThinDbObjectImpl)
|
||||||
|
copied_impl.type = self.type
|
||||||
|
copied_impl.flags = self.flags
|
||||||
|
copied_impl.image_flags = self.image_flags
|
||||||
|
copied_impl.image_version = self.image_version
|
||||||
|
copied_impl.toid = self.toid
|
||||||
|
copied_impl.packed_data = self.packed_data
|
||||||
|
copied_impl.num_elements = self.num_elements
|
||||||
|
if self.unpacked_attrs is not None:
|
||||||
|
copied_impl.unpacked_attrs = self.unpacked_attrs.copy()
|
||||||
|
if self.unpacked_array is not None:
|
||||||
|
copied_impl.unpacked_array = list(self.unpacked_array)
|
||||||
|
return copied_impl
|
||||||
|
|
||||||
|
def delete_by_index(self, int32_t index):
|
||||||
|
"""
|
||||||
|
Internal method for deleting an entry from a collection that is indexed
|
||||||
|
by integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array is not None:
|
||||||
|
del self.unpacked_array[index]
|
||||||
|
else:
|
||||||
|
self.unpacked_assoc_keys = None
|
||||||
|
del self.unpacked_assoc_array[index]
|
||||||
|
|
||||||
|
def exists_by_index(self, int32_t index):
|
||||||
|
"""
|
||||||
|
Internal method for determining if an entry exists in a collection that
|
||||||
|
is indexed by integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array is not None:
|
||||||
|
return index >= 0 and index < len(self.unpacked_array)
|
||||||
|
else:
|
||||||
|
return index in self.unpacked_assoc_array
|
||||||
|
|
||||||
|
def get_attr_value(self, ThinDbObjectAttrImpl attr):
|
||||||
|
"""
|
||||||
|
Internal method for getting an attribute value.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
return self.unpacked_attrs[attr.name]
|
||||||
|
|
||||||
|
def get_element_by_index(self, int32_t index):
|
||||||
|
"""
|
||||||
|
Internal method for getting an entry from a collection that is indexed
|
||||||
|
by integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array:
|
||||||
|
return self.unpacked_array[index]
|
||||||
|
elif self.unpacked_assoc_array:
|
||||||
|
return self.unpacked_assoc_array[index]
|
||||||
|
|
||||||
|
def get_first_index(self):
|
||||||
|
"""
|
||||||
|
Internal method for getting the first index from a collection that is
|
||||||
|
indexed by integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array:
|
||||||
|
return 0
|
||||||
|
elif self.unpacked_assoc_array:
|
||||||
|
self._ensure_assoc_keys()
|
||||||
|
return self.unpacked_assoc_keys[0]
|
||||||
|
|
||||||
|
def get_last_index(self):
|
||||||
|
"""
|
||||||
|
Internal method for getting the last index from a collection that is
|
||||||
|
indexed by integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array:
|
||||||
|
return len(self.unpacked_array) - 1
|
||||||
|
elif self.unpacked_assoc_array:
|
||||||
|
self._ensure_assoc_keys()
|
||||||
|
return self.unpacked_assoc_keys[-1]
|
||||||
|
|
||||||
|
def get_next_index(self, int32_t index):
|
||||||
|
"""
|
||||||
|
Internal method for getting the next index from a collection that is
|
||||||
|
indexed by integers.
|
||||||
|
"""
|
||||||
|
cdef int32_t i
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array:
|
||||||
|
if index + 1 < len(self.unpacked_array):
|
||||||
|
return index + 1
|
||||||
|
elif self.unpacked_assoc_array:
|
||||||
|
self._ensure_assoc_keys()
|
||||||
|
for i in self.unpacked_assoc_keys:
|
||||||
|
if i > index:
|
||||||
|
return i
|
||||||
|
|
||||||
|
def get_prev_index(self, int32_t index):
|
||||||
|
"""
|
||||||
|
Internal method for getting the next index from a collection that is
|
||||||
|
indexed by integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array:
|
||||||
|
if index > 0:
|
||||||
|
return index - 1
|
||||||
|
elif self.unpacked_assoc_array:
|
||||||
|
self._ensure_assoc_keys()
|
||||||
|
for i in reversed(self.unpacked_assoc_keys):
|
||||||
|
if i < index:
|
||||||
|
return i
|
||||||
|
|
||||||
|
def get_size(self):
|
||||||
|
"""
|
||||||
|
Internal method for getting the size of a collection.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array is not None:
|
||||||
|
return len(self.unpacked_array)
|
||||||
|
else:
|
||||||
|
return len(self.unpacked_assoc_array)
|
||||||
|
|
||||||
|
def set_attr_value_checked(self, ThinDbObjectAttrImpl attr, object value):
|
||||||
|
"""
|
||||||
|
Internal method for setting an attribute value.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
self.unpacked_attrs[attr.name] = value
|
||||||
|
|
||||||
|
def set_element_by_index_checked(self, int32_t index, object value):
|
||||||
|
"""
|
||||||
|
Internal method for setting an entry in a collection that is indexed by
|
||||||
|
integers.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if self.unpacked_array is not None:
|
||||||
|
self.unpacked_array[index] = value
|
||||||
|
else:
|
||||||
|
if index not in self.unpacked_assoc_array:
|
||||||
|
self.unpacked_assoc_keys = None
|
||||||
|
self.unpacked_assoc_array[index] = value
|
||||||
|
|
||||||
|
def trim(self, int32_t num_to_trim):
|
||||||
|
"""
|
||||||
|
Internal method for trimming a number of entries from a collection.
|
||||||
|
"""
|
||||||
|
self._ensure_unpacked()
|
||||||
|
if num_to_trim > 0:
|
||||||
|
self.unpacked_array = self.unpacked_array[:-num_to_trim]
|
||||||
|
|
||||||
|
|
||||||
|
cdef class ThinDbObjectAttrImpl(BaseDbObjectAttrImpl):
|
||||||
|
cdef:
|
||||||
|
bytes oid
|
||||||
|
|
||||||
|
|
||||||
|
cdef class ThinDbObjectTypeImpl(BaseDbObjectTypeImpl):
|
||||||
|
cdef:
|
||||||
|
uint8_t collection_type, collection_flags, version
|
||||||
|
uint32_t max_num_elements
|
||||||
|
bytes oid
|
||||||
|
|
||||||
|
def create_new_object(self):
|
||||||
|
"""
|
||||||
|
Internal method for creating a new object.
|
||||||
|
"""
|
||||||
|
cdef ThinDbObjectImpl obj_impl
|
||||||
|
obj_impl = ThinDbObjectImpl.__new__(ThinDbObjectImpl)
|
||||||
|
obj_impl.type = self
|
||||||
|
obj_impl.toid = b'\x00\x22' + \
|
||||||
|
bytes([TNS_OBJ_NON_NULL_OID, TNS_OBJ_HAS_EXTENT_OID]) + \
|
||||||
|
self.oid + TNS_EXTENT_OID
|
||||||
|
obj_impl.flags = TNS_OBJ_TOP_LEVEL
|
||||||
|
obj_impl.image_flags = TNS_OBJ_IS_VERSION_81
|
||||||
|
obj_impl.image_version = TNS_OBJ_IMAGE_VERSION
|
||||||
|
obj_impl.unpacked_attrs = {}
|
||||||
|
if self.is_collection:
|
||||||
|
obj_impl.image_flags |= TNS_OBJ_IS_COLLECTION
|
||||||
|
if self.collection_type == TNS_OBJ_PLSQL_INDEX_TABLE:
|
||||||
|
obj_impl.unpacked_assoc_array = {}
|
||||||
|
else:
|
||||||
|
obj_impl.unpacked_array = []
|
||||||
|
else:
|
||||||
|
obj_impl.image_flags |= TNS_OBJ_NO_PREFIX_SEG
|
||||||
|
for attr in self.attrs:
|
||||||
|
obj_impl.unpacked_attrs[attr.name] = None
|
||||||
|
return obj_impl
|
||||||
|
|
||||||
|
|
||||||
|
cdef class ThinDbObjectTypeSuperCache:
|
||||||
|
cdef:
|
||||||
|
dict caches
|
||||||
|
object lock
|
||||||
|
int cache_num
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.caches = {}
|
||||||
|
self.cache_num = 0
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
cdef class ThinDbObjectTypeCache:
|
||||||
|
cdef:
|
||||||
|
object return_value_var, full_name_var, oid_var, tds_var
|
||||||
|
object meta_cursor, attrs_ref_cursor_var, version_var
|
||||||
|
object schema_var, package_name_var, name_var
|
||||||
|
ThinConnImpl conn_impl
|
||||||
|
dict types_by_oid
|
||||||
|
dict types_by_name
|
||||||
|
list partial_types
|
||||||
|
|
||||||
|
cdef int _determine_element_type_csfrm(self, ThinDbObjectTypeImpl typ_impl,
|
||||||
|
uint8_t* csfrm) except -1:
|
||||||
|
"""
|
||||||
|
Determine the element type's character set form. This is only needed
|
||||||
|
for CLOB and NCLOB where this information is not stored in the TDS.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
object cursor
|
||||||
|
str type_name
|
||||||
|
cursor = self.meta_cursor.connection.cursor()
|
||||||
|
if typ_impl.package_name is not None:
|
||||||
|
cursor.execute("""
|
||||||
|
select elem_type_name
|
||||||
|
from all_plsql_coll_types
|
||||||
|
where owner = :owner
|
||||||
|
and package_name = :package_name
|
||||||
|
and type_name = :name""",
|
||||||
|
owner=typ_impl.schema,
|
||||||
|
package_name=typ_impl.package_name,
|
||||||
|
name=typ_impl.name)
|
||||||
|
else:
|
||||||
|
cursor.execute("""
|
||||||
|
select elem_type_name
|
||||||
|
from all_coll_types
|
||||||
|
where owner = :owner
|
||||||
|
and type_name = :name""",
|
||||||
|
owner=typ_impl.schema,
|
||||||
|
name=typ_impl.name)
|
||||||
|
|
||||||
|
type_name, = cursor.fetchone()
|
||||||
|
if type_name == "NCLOB":
|
||||||
|
csfrm[0] = TNS_CS_NCHAR
|
||||||
|
else:
|
||||||
|
csfrm[0] = TNS_CS_IMPLICIT
|
||||||
|
|
||||||
|
cdef int _determine_element_objtype(self,
|
||||||
|
ThinDbObjectTypeImpl impl) except -1:
|
||||||
|
"""
|
||||||
|
Determine the element type's object type. This is needed when
|
||||||
|
processing collections with object as the element type since this
|
||||||
|
information is not available in the TDS.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
str schema, name, package_name = None
|
||||||
|
object cursor
|
||||||
|
cursor = self.meta_cursor.connection.cursor()
|
||||||
|
if impl.package_name is not None:
|
||||||
|
cursor.execute("""
|
||||||
|
select
|
||||||
|
elem_type_owner,
|
||||||
|
elem_type_package,
|
||||||
|
elem_type_name
|
||||||
|
from all_plsql_coll_types
|
||||||
|
where owner = :owner
|
||||||
|
and package_name = :package_name
|
||||||
|
and type_name = :name""",
|
||||||
|
owner=impl.schema,
|
||||||
|
package_name=impl.package_name,
|
||||||
|
name=impl.name)
|
||||||
|
schema, package_name, name = cursor.fetchone()
|
||||||
|
else:
|
||||||
|
cursor.execute("""
|
||||||
|
select
|
||||||
|
elem_type_owner,
|
||||||
|
elem_type_name
|
||||||
|
from all_coll_types
|
||||||
|
where owner = :owner
|
||||||
|
and type_name = :name""",
|
||||||
|
owner=impl.schema,
|
||||||
|
name=impl.name)
|
||||||
|
schema, name = cursor.fetchone()
|
||||||
|
impl.element_objtype = self.get_type_for_info(None, schema,
|
||||||
|
package_name, name)
|
||||||
|
|
||||||
|
cdef int _initialize(self, ThinConnImpl conn_impl) except -1:
|
||||||
|
self.types_by_oid = {}
|
||||||
|
self.types_by_name = {}
|
||||||
|
self.partial_types = []
|
||||||
|
self.conn_impl = conn_impl
|
||||||
|
|
||||||
|
cdef int _init_meta_cursor(self, object conn) except -1:
|
||||||
|
"""
|
||||||
|
Initializes the cursor that fetches the type metadata.
|
||||||
|
"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
self.return_value_var = cursor.var(DB_TYPE_BINARY_INTEGER)
|
||||||
|
self.tds_var = cursor.var(bytes)
|
||||||
|
self.full_name_var = cursor.var(str)
|
||||||
|
self.schema_var = cursor.var(str)
|
||||||
|
self.package_name_var = cursor.var(str)
|
||||||
|
self.name_var = cursor.var(str)
|
||||||
|
self.oid_var = cursor.var(bytes)
|
||||||
|
self.version_var = cursor.var(DB_TYPE_BINARY_INTEGER)
|
||||||
|
self.attrs_ref_cursor_var = cursor.var(DB_TYPE_CURSOR)
|
||||||
|
cursor.setinputsizes(ret_val=self.return_value_var,
|
||||||
|
tds=self.tds_var,
|
||||||
|
full_name=self.full_name_var,
|
||||||
|
oid=self.oid_var,
|
||||||
|
schema=self.schema_var,
|
||||||
|
package_name=self.package_name_var,
|
||||||
|
name=self.name_var,
|
||||||
|
version=self.version_var,
|
||||||
|
attrs_rc=self.attrs_ref_cursor_var)
|
||||||
|
cursor.prepare("""
|
||||||
|
declare
|
||||||
|
t_Instantiable varchar2(3);
|
||||||
|
t_SuperTypeOwner varchar2(128);
|
||||||
|
t_SuperTypeName varchar2(128);
|
||||||
|
t_SubTypeRefCursor sys_refcursor;
|
||||||
|
begin
|
||||||
|
:ret_val := dbms_pickler.get_type_shape(:full_name, :oid,
|
||||||
|
:version, :tds, t_Instantiable, t_SuperTypeOwner,
|
||||||
|
t_SuperTypeName, :attrs_rc, t_SubTypeRefCursor);
|
||||||
|
:package_name := null;
|
||||||
|
begin
|
||||||
|
select owner, type_name
|
||||||
|
into :schema, :name
|
||||||
|
from all_types
|
||||||
|
where type_oid = :oid;
|
||||||
|
exception
|
||||||
|
when no_data_found then
|
||||||
|
select owner, package_name, type_name
|
||||||
|
into :schema, :package_name, :name
|
||||||
|
from all_plsql_types
|
||||||
|
where type_oid = :oid;
|
||||||
|
end;
|
||||||
|
end;""")
|
||||||
|
self.meta_cursor = cursor
|
||||||
|
|
||||||
|
cdef int _parse_element_type(self, ThinDbObjectTypeImpl typ_impl,
|
||||||
|
TDSBuffer buf) except -1:
|
||||||
|
"""
|
||||||
|
Parses the element type from the TDS buffer.
|
||||||
|
"""
|
||||||
|
cdef uint8_t attr_type, ora_type_num = 0, csfrm = 0
|
||||||
|
buf.read_ub1(&attr_type)
|
||||||
|
if attr_type in (TNS_OBJ_TDS_TYPE_NUMBER, TNS_OBJ_TDS_TYPE_FLOAT):
|
||||||
|
ora_type_num = TNS_DATA_TYPE_NUMBER
|
||||||
|
elif attr_type in (TNS_OBJ_TDS_TYPE_VARCHAR, TNS_OBJ_TDS_TYPE_CHAR):
|
||||||
|
buf.skip_raw_bytes(2) # maximum length
|
||||||
|
buf.read_ub1(&csfrm)
|
||||||
|
csfrm = csfrm & 0x7f
|
||||||
|
if attr_type == TNS_OBJ_TDS_TYPE_VARCHAR:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_VARCHAR
|
||||||
|
else:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_CHAR
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_RAW:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_RAW
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_BINARY_FLOAT:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_BINARY_FLOAT
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_BINARY_DOUBLE:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_BINARY_DOUBLE
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_DATE:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_DATE
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_TIMESTAMP:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_TIMESTAMP
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_TIMESTAMP_LTZ:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_TIMESTAMP_LTZ
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_TIMESTAMP_TZ:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_TIMESTAMP_TZ
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_BOOLEAN:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_BOOLEAN
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_CLOB:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_CLOB
|
||||||
|
self._determine_element_type_csfrm(typ_impl, &csfrm)
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_BLOB:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_BLOB
|
||||||
|
elif attr_type == TNS_OBJ_TDS_TYPE_OBJ:
|
||||||
|
ora_type_num = TNS_DATA_TYPE_INT_NAMED
|
||||||
|
self._determine_element_objtype(typ_impl)
|
||||||
|
else:
|
||||||
|
errors._raise_err(errors.ERR_TDS_TYPE_NOT_SUPPORTED, num=attr_type)
|
||||||
|
typ_impl.element_dbtype = DbType._from_ora_type_and_csfrm(ora_type_num,
|
||||||
|
csfrm)
|
||||||
|
|
||||||
|
cdef int _parse_tds(self, ThinDbObjectTypeImpl typ_impl,
|
||||||
|
bytes tds) except -1:
|
||||||
|
"""
|
||||||
|
Parses the TDS for the type. This is only needed for collection types,
|
||||||
|
so if the TDS is determined to be for an object type, the remaining
|
||||||
|
information is skipped.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint32_t element_pos
|
||||||
|
uint16_t num_attrs
|
||||||
|
uint8_t attr_type
|
||||||
|
TDSBuffer buf
|
||||||
|
|
||||||
|
# parse initial TDS bytes
|
||||||
|
buf = TDSBuffer.__new__(TDSBuffer)
|
||||||
|
buf._populate_from_bytes(tds)
|
||||||
|
buf.skip_raw_bytes(4) # end offset
|
||||||
|
buf.skip_raw_bytes(2) # version op code and version
|
||||||
|
buf.skip_raw_bytes(2) # unknown
|
||||||
|
|
||||||
|
# if the number of attributes exceeds 1, the type cannot refer to a
|
||||||
|
# collection, so nothing further needs to be done
|
||||||
|
buf.read_uint16(&num_attrs)
|
||||||
|
if num_attrs > 1:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# continue parsing TDS bytes to discover if type refers to a collection
|
||||||
|
buf.skip_raw_bytes(1) # TDS attributes?
|
||||||
|
buf.skip_raw_bytes(1) # start ADT op code
|
||||||
|
buf.skip_raw_bytes(2) # ADT number (always zero)
|
||||||
|
buf.skip_raw_bytes(4) # offset to index table
|
||||||
|
|
||||||
|
# if type of first attribute is not a collection, nothing further needs
|
||||||
|
# to be done
|
||||||
|
buf.read_ub1(&attr_type)
|
||||||
|
if attr_type != TNS_OBJ_TDS_TYPE_COLL:
|
||||||
|
return 0
|
||||||
|
typ_impl.is_collection = True
|
||||||
|
|
||||||
|
# continue parsing TDS to determine element type
|
||||||
|
buf.read_uint32(&element_pos)
|
||||||
|
buf.read_uint32(&typ_impl.max_num_elements)
|
||||||
|
buf.read_ub1(&typ_impl.collection_type)
|
||||||
|
if typ_impl.collection_type == TNS_OBJ_PLSQL_INDEX_TABLE:
|
||||||
|
typ_impl.collection_flags = TNS_OBJ_HAS_INDEXES
|
||||||
|
buf.skip_to(element_pos)
|
||||||
|
self._parse_element_type(typ_impl, buf)
|
||||||
|
|
||||||
|
cdef int _populate_type_info(self, str name,
|
||||||
|
ThinDbObjectTypeImpl typ_impl) except -1:
|
||||||
|
"""
|
||||||
|
Populate the type information given the name of the type.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ThinDbObjectAttrImpl attr_impl
|
||||||
|
ssize_t pos, name_length
|
||||||
|
list name_components
|
||||||
|
object attrs_rc
|
||||||
|
self.full_name_var.setvalue(0, name)
|
||||||
|
self.meta_cursor.execute(None)
|
||||||
|
if self.return_value_var.getvalue() != 0:
|
||||||
|
errors._raise_err(errors.ERR_INVALID_OBJECT_TYPE_NAME, name=name)
|
||||||
|
typ_impl.version = self.version_var.getvalue()
|
||||||
|
if typ_impl.oid is None:
|
||||||
|
typ_impl.oid = self.oid_var.getvalue()
|
||||||
|
self.types_by_oid[typ_impl.oid] = typ_impl
|
||||||
|
if typ_impl.schema is None:
|
||||||
|
typ_impl.schema = self.schema_var.getvalue()
|
||||||
|
typ_impl.package_name = self.package_name_var.getvalue()
|
||||||
|
typ_impl.name = self.name_var.getvalue()
|
||||||
|
self._parse_tds(typ_impl, self.tds_var.getvalue())
|
||||||
|
typ_impl.attrs = []
|
||||||
|
typ_impl.attrs_by_name = {}
|
||||||
|
attrs_rc = self.attrs_ref_cursor_var.getvalue()
|
||||||
|
for cursor_version, attr_name, attr_num, attr_type_name, \
|
||||||
|
attr_type_owner, attr_type_package, attr_type_oid, \
|
||||||
|
attr_instantiable, attr_super_type_owner, \
|
||||||
|
attr_super_type_name in attrs_rc:
|
||||||
|
attr_impl = ThinDbObjectAttrImpl.__new__(ThinDbObjectAttrImpl)
|
||||||
|
attr_impl.name = attr_name
|
||||||
|
if attr_type_owner is not None:
|
||||||
|
attr_impl.dbtype = DB_TYPE_OBJECT
|
||||||
|
attr_impl.objtype = self.get_type_for_info(attr_type_oid,
|
||||||
|
attr_type_owner,
|
||||||
|
attr_type_package,
|
||||||
|
attr_type_name)
|
||||||
|
else:
|
||||||
|
attr_impl.dbtype = DbType._from_ora_name(attr_type_name)
|
||||||
|
typ_impl.attrs.append(attr_impl)
|
||||||
|
typ_impl.attrs_by_name[attr_name] = attr_impl
|
||||||
|
|
||||||
|
cdef ThinDbObjectTypeImpl get_type(self, object conn, str name):
|
||||||
|
"""
|
||||||
|
Returns the database object type given its name. The cache is first
|
||||||
|
searched and if it is not found, the database is searched and the
|
||||||
|
result stored in the cache.
|
||||||
|
"""
|
||||||
|
cdef ThinDbObjectTypeImpl typ_impl
|
||||||
|
typ_impl = self.types_by_name.get(name)
|
||||||
|
if typ_impl is None:
|
||||||
|
if self.meta_cursor is None:
|
||||||
|
self._init_meta_cursor(conn)
|
||||||
|
typ_impl = ThinDbObjectTypeImpl.__new__(ThinDbObjectTypeImpl)
|
||||||
|
typ_impl._conn_impl = self.conn_impl
|
||||||
|
self._populate_type_info(name, typ_impl)
|
||||||
|
self.types_by_oid[typ_impl.oid] = typ_impl
|
||||||
|
self.types_by_name[name] = typ_impl
|
||||||
|
self.populate_partial_types(conn)
|
||||||
|
return typ_impl
|
||||||
|
|
||||||
|
cdef ThinDbObjectTypeImpl get_type_for_info(self, bytes oid, str schema,
|
||||||
|
str package_name, str name):
|
||||||
|
"""
|
||||||
|
Returns a type for the specified fetch info, if one has already been
|
||||||
|
cached. If not, a new type object is created and cached. It is also
|
||||||
|
added to the partial_types list which will be fully populated once the
|
||||||
|
current execute has completed.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ThinDbObjectTypeImpl typ_impl
|
||||||
|
str full_name
|
||||||
|
if package_name is not None:
|
||||||
|
full_name = f"{schema}.{package_name}.{name}"
|
||||||
|
else:
|
||||||
|
full_name = f"{schema}.{name}"
|
||||||
|
if oid is not None:
|
||||||
|
typ_impl = self.types_by_oid.get(oid)
|
||||||
|
else:
|
||||||
|
typ_impl = self.types_by_name.get(full_name)
|
||||||
|
if typ_impl is None:
|
||||||
|
typ_impl = ThinDbObjectTypeImpl.__new__(ThinDbObjectTypeImpl)
|
||||||
|
typ_impl._conn_impl = self.conn_impl
|
||||||
|
typ_impl.oid = oid
|
||||||
|
typ_impl.schema = schema
|
||||||
|
typ_impl.package_name = package_name
|
||||||
|
typ_impl.name = name
|
||||||
|
if oid is not None:
|
||||||
|
self.types_by_oid[oid] = typ_impl
|
||||||
|
self.types_by_name[full_name] = typ_impl
|
||||||
|
self.partial_types.append(typ_impl)
|
||||||
|
return typ_impl
|
||||||
|
|
||||||
|
cdef int populate_partial_types(self, object conn) except -1:
|
||||||
|
"""
|
||||||
|
Populate any partial types that were discovered earlier. Since
|
||||||
|
populating an object type might result in additional object types being
|
||||||
|
discovered, object types are popped from the partial types list until
|
||||||
|
the list is empty.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ThinDbObjectTypeImpl typ_impl
|
||||||
|
str full_name
|
||||||
|
while self.partial_types:
|
||||||
|
typ_impl = self.partial_types.pop()
|
||||||
|
if self.meta_cursor is None:
|
||||||
|
self._init_meta_cursor(conn)
|
||||||
|
if typ_impl.package_name is not None:
|
||||||
|
full_name = f'"{typ_impl.schema}".' + \
|
||||||
|
f'"{typ_impl.package_name}".' + \
|
||||||
|
f'"{typ_impl.name}"'
|
||||||
|
else:
|
||||||
|
full_name = f'"{typ_impl.schema}"."{typ_impl.name}"'
|
||||||
|
self._populate_type_info(full_name, typ_impl)
|
||||||
|
|
||||||
|
|
||||||
|
# global cache of database object types
|
||||||
|
# since the database object types require a reference to the connection (in
|
||||||
|
# order to be able to manage LOBs), storing the cache on the connection would
|
||||||
|
# involve creating a circular reference
|
||||||
|
cdef ThinDbObjectTypeSuperCache DB_OBJECT_TYPE_SUPER_CACHE = \
|
||||||
|
ThinDbObjectTypeSuperCache()
|
||||||
|
|
||||||
|
|
||||||
|
cdef int create_new_dbobject_type_cache(ThinConnImpl conn_impl) except -1:
|
||||||
|
"""
|
||||||
|
Creates a new database object type cache and returns its identifier.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ThinDbObjectTypeCache cache
|
||||||
|
int cache_num
|
||||||
|
with DB_OBJECT_TYPE_SUPER_CACHE.lock:
|
||||||
|
DB_OBJECT_TYPE_SUPER_CACHE.cache_num += 1
|
||||||
|
cache_num = DB_OBJECT_TYPE_SUPER_CACHE.cache_num
|
||||||
|
cache = ThinDbObjectTypeCache.__new__(ThinDbObjectTypeCache)
|
||||||
|
cache._initialize(conn_impl)
|
||||||
|
DB_OBJECT_TYPE_SUPER_CACHE.caches[cache_num] = cache
|
||||||
|
return cache_num
|
||||||
|
|
||||||
|
|
||||||
|
cdef ThinDbObjectTypeCache get_dbobject_type_cache(int cache_num):
|
||||||
|
"""
|
||||||
|
Returns the database object type cache given its identifier.
|
||||||
|
"""
|
||||||
|
return DB_OBJECT_TYPE_SUPER_CACHE.caches[cache_num]
|
||||||
|
|
||||||
|
|
||||||
|
cdef int remove_dbobject_type_cache(int cache_num) except -1:
|
||||||
|
"""
|
||||||
|
Removes the sub cache given its identifier.
|
||||||
|
"""
|
||||||
|
del DB_OBJECT_TYPE_SUPER_CACHE.caches[cache_num]
|
|
@ -290,6 +290,7 @@ cdef class Message:
|
||||||
|
|
||||||
cdef class MessageWithData(Message):
|
cdef class MessageWithData(Message):
|
||||||
cdef:
|
cdef:
|
||||||
|
ThinDbObjectTypeCache type_cache
|
||||||
ThinCursorImpl cursor_impl
|
ThinCursorImpl cursor_impl
|
||||||
array.array bit_vector_buf
|
array.array bit_vector_buf
|
||||||
const char_type *bit_vector
|
const char_type *bit_vector
|
||||||
|
@ -521,6 +522,7 @@ cdef class MessageWithData(Message):
|
||||||
uint8_t num_bytes, ora_type_num, csfrm
|
uint8_t num_bytes, ora_type_num, csfrm
|
||||||
ThinCursorImpl cursor_impl
|
ThinCursorImpl cursor_impl
|
||||||
object column_value = None
|
object column_value = None
|
||||||
|
ThinDbObjectImpl obj_impl
|
||||||
int32_t actual_num_bytes
|
int32_t actual_num_bytes
|
||||||
uint32_t buffer_size
|
uint32_t buffer_size
|
||||||
FetchInfo fetch_info
|
FetchInfo fetch_info
|
||||||
|
@ -536,9 +538,10 @@ cdef class MessageWithData(Message):
|
||||||
buffer_size = var_impl.buffer_size
|
buffer_size = var_impl.buffer_size
|
||||||
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 self.in_fetch \
|
||||||
and ora_type_num != TNS_DATA_TYPE_LONG_RAW \
|
and ora_type_num not in (TNS_DATA_TYPE_LONG,
|
||||||
and ora_type_num != TNS_DATA_TYPE_UROWID:
|
TNS_DATA_TYPE_LONG_RAW,
|
||||||
|
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 \
|
||||||
|
@ -591,7 +594,17 @@ cdef class MessageWithData(Message):
|
||||||
elif ora_type_num == TNS_DATA_TYPE_INTERVAL_DS:
|
elif ora_type_num == TNS_DATA_TYPE_INTERVAL_DS:
|
||||||
column_value = buf.read_interval_ds()
|
column_value = buf.read_interval_ds()
|
||||||
elif ora_type_num in (TNS_DATA_TYPE_CLOB, TNS_DATA_TYPE_BLOB):
|
elif ora_type_num in (TNS_DATA_TYPE_CLOB, TNS_DATA_TYPE_BLOB):
|
||||||
column_value = buf.read_lob(self.conn_impl, var_impl.dbtype)
|
column_value = buf.read_lob_with_length(self.conn_impl,
|
||||||
|
var_impl.dbtype)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_INT_NAMED:
|
||||||
|
obj_impl = buf.read_dbobject(var_impl.objtype)
|
||||||
|
if obj_impl is not None:
|
||||||
|
if not self.in_fetch:
|
||||||
|
column_value = var_impl._values[pos]
|
||||||
|
if column_value is not None:
|
||||||
|
column_value._impl = obj_impl
|
||||||
|
else:
|
||||||
|
column_value = PY_TYPE_DB_OBJECT._from_impl(obj_impl)
|
||||||
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)
|
||||||
|
@ -615,11 +628,15 @@ cdef class MessageWithData(Message):
|
||||||
cdef FetchInfo _process_column_info(self, ReadBuffer buf,
|
cdef FetchInfo _process_column_info(self, ReadBuffer buf,
|
||||||
ThinCursorImpl cursor_impl):
|
ThinCursorImpl cursor_impl):
|
||||||
cdef:
|
cdef:
|
||||||
|
ThinDbObjectTypeImpl typ_impl
|
||||||
uint8_t data_type, csfrm
|
uint8_t data_type, csfrm
|
||||||
int8_t precision, scale
|
int8_t precision, scale
|
||||||
uint8_t nulls_allowed
|
uint8_t nulls_allowed
|
||||||
FetchInfo fetch_info
|
FetchInfo fetch_info
|
||||||
uint32_t num_bytes
|
uint32_t num_bytes
|
||||||
|
str schema, name
|
||||||
|
int cache_num
|
||||||
|
bytes oid
|
||||||
buf.read_ub1(&data_type)
|
buf.read_ub1(&data_type)
|
||||||
fetch_info = FetchInfo()
|
fetch_info = FetchInfo()
|
||||||
buf.skip_ub1() # flags
|
buf.skip_ub1() # flags
|
||||||
|
@ -639,7 +656,7 @@ cdef class MessageWithData(Message):
|
||||||
buf.skip_ub4() # cont flags
|
buf.skip_ub4() # cont flags
|
||||||
buf.read_ub4(&num_bytes) # OID
|
buf.read_ub4(&num_bytes) # OID
|
||||||
if num_bytes > 0:
|
if num_bytes > 0:
|
||||||
buf.skip_raw_bytes(num_bytes + 1)
|
oid = buf.read_bytes()
|
||||||
buf.skip_ub2() # version
|
buf.skip_ub2() # version
|
||||||
buf.skip_ub2() # character set id
|
buf.skip_ub2() # character set id
|
||||||
buf.read_ub1(&csfrm) # character set form
|
buf.read_ub1(&csfrm) # character set form
|
||||||
|
@ -657,14 +674,19 @@ cdef class MessageWithData(Message):
|
||||||
fetch_info._name = buf.read_str(TNS_CS_IMPLICIT)
|
fetch_info._name = buf.read_str(TNS_CS_IMPLICIT)
|
||||||
buf.read_ub4(&num_bytes)
|
buf.read_ub4(&num_bytes)
|
||||||
if num_bytes > 0:
|
if num_bytes > 0:
|
||||||
buf.skip_ub1() # skip repeated length
|
schema = buf.read_str(TNS_CS_IMPLICIT)
|
||||||
buf.skip_raw_bytes(num_bytes) # schema name
|
|
||||||
buf.read_ub4(&num_bytes)
|
buf.read_ub4(&num_bytes)
|
||||||
if num_bytes > 0:
|
if num_bytes > 0:
|
||||||
buf.skip_ub1() # skip repeated length
|
name = buf.read_str(TNS_CS_IMPLICIT)
|
||||||
buf.skip_raw_bytes(num_bytes) # type name
|
|
||||||
buf.skip_ub2() # column position
|
buf.skip_ub2() # column position
|
||||||
buf.skip_ub4() # uds flag
|
buf.skip_ub4() # uds flag
|
||||||
|
if data_type == TNS_DATA_TYPE_INT_NAMED:
|
||||||
|
if self.type_cache is None:
|
||||||
|
cache_num = self.conn_impl._dbobject_type_cache_num
|
||||||
|
self.type_cache = get_dbobject_type_cache(cache_num)
|
||||||
|
typ_impl = self.type_cache.get_type_for_info(oid, schema, None,
|
||||||
|
name)
|
||||||
|
fetch_info._objtype = typ_impl
|
||||||
return fetch_info
|
return fetch_info
|
||||||
|
|
||||||
cdef int _process_describe_info(self, ReadBuffer buf,
|
cdef int _process_describe_info(self, ReadBuffer buf,
|
||||||
|
@ -894,6 +916,7 @@ cdef class MessageWithData(Message):
|
||||||
cdef int _write_column_metadata(self, WriteBuffer buf,
|
cdef int _write_column_metadata(self, WriteBuffer buf,
|
||||||
list bind_var_impls) except -1:
|
list bind_var_impls) except -1:
|
||||||
cdef:
|
cdef:
|
||||||
|
ThinDbObjectTypeImpl typ_impl
|
||||||
uint8_t ora_type_num, flag
|
uint8_t ora_type_num, flag
|
||||||
uint32_t buffer_size
|
uint32_t buffer_size
|
||||||
ThinVarImpl var_impl
|
ThinVarImpl var_impl
|
||||||
|
@ -921,8 +944,14 @@ cdef class MessageWithData(Message):
|
||||||
else:
|
else:
|
||||||
buf.write_ub4(0) # max num elements
|
buf.write_ub4(0) # max num elements
|
||||||
buf.write_ub4(0) # cont flag
|
buf.write_ub4(0) # cont flag
|
||||||
buf.write_ub4(0) # OID
|
if var_impl.objtype is not None:
|
||||||
buf.write_ub4(0) # version
|
typ_impl = var_impl.objtype
|
||||||
|
buf.write_ub4(len(typ_impl.oid))
|
||||||
|
buf.write_bytes_with_length(typ_impl.oid)
|
||||||
|
buf.write_ub4(typ_impl.version)
|
||||||
|
else:
|
||||||
|
buf.write_ub4(0) # OID
|
||||||
|
buf.write_ub4(0) # version
|
||||||
if var_impl.dbtype._csfrm != 0:
|
if var_impl.dbtype._csfrm != 0:
|
||||||
buf.write_ub4(TNS_CHARSET_UTF8)
|
buf.write_ub4(TNS_CHARSET_UTF8)
|
||||||
else:
|
else:
|
||||||
|
@ -937,6 +966,7 @@ cdef class MessageWithData(Message):
|
||||||
object value) except -1:
|
object value) except -1:
|
||||||
cdef:
|
cdef:
|
||||||
uint8_t ora_type_num = var_impl.dbtype._ora_type_num
|
uint8_t ora_type_num = var_impl.dbtype._ora_type_num
|
||||||
|
ThinDbObjectTypeImpl typ_impl
|
||||||
ThinCursorImpl cursor_impl
|
ThinCursorImpl cursor_impl
|
||||||
ThinLobImpl lob_impl
|
ThinLobImpl lob_impl
|
||||||
uint32_t num_bytes
|
uint32_t num_bytes
|
||||||
|
@ -945,6 +975,13 @@ cdef class MessageWithData(Message):
|
||||||
if ora_type_num == TNS_DATA_TYPE_BOOLEAN:
|
if ora_type_num == TNS_DATA_TYPE_BOOLEAN:
|
||||||
buf.write_uint8(TNS_ESCAPE_CHAR)
|
buf.write_uint8(TNS_ESCAPE_CHAR)
|
||||||
buf.write_uint8(1)
|
buf.write_uint8(1)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_INT_NAMED:
|
||||||
|
buf.write_ub4(0) # TOID
|
||||||
|
buf.write_ub4(0) # OID
|
||||||
|
buf.write_ub4(0) # snapshot
|
||||||
|
buf.write_ub4(0) # version
|
||||||
|
buf.write_ub4(0) # packed data length
|
||||||
|
buf.write_ub4(TNS_OBJ_TOP_LEVEL) # flags
|
||||||
else:
|
else:
|
||||||
buf.write_uint8(0)
|
buf.write_uint8(0)
|
||||||
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
|
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
|
||||||
|
@ -955,10 +992,10 @@ cdef class MessageWithData(Message):
|
||||||
else:
|
else:
|
||||||
buf._caps._check_ncharset_id()
|
buf._caps._check_ncharset_id()
|
||||||
temp_bytes = (<str> value).encode(TNS_ENCODING_UTF16)
|
temp_bytes = (<str> value).encode(TNS_ENCODING_UTF16)
|
||||||
buf.write_bytes_chunked(temp_bytes)
|
buf.write_bytes_with_length(temp_bytes)
|
||||||
elif ora_type_num == TNS_DATA_TYPE_RAW \
|
elif ora_type_num == TNS_DATA_TYPE_RAW \
|
||||||
or ora_type_num == TNS_DATA_TYPE_LONG_RAW:
|
or ora_type_num == TNS_DATA_TYPE_LONG_RAW:
|
||||||
buf.write_bytes_chunked(value)
|
buf.write_bytes_with_length(value)
|
||||||
elif ora_type_num == TNS_DATA_TYPE_NUMBER \
|
elif ora_type_num == TNS_DATA_TYPE_NUMBER \
|
||||||
or ora_type_num == TNS_DATA_TYPE_BINARY_INTEGER:
|
or ora_type_num == TNS_DATA_TYPE_BINARY_INTEGER:
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
|
@ -986,22 +1023,17 @@ cdef class MessageWithData(Message):
|
||||||
buf.write_ub4(1)
|
buf.write_ub4(1)
|
||||||
buf.write_ub4(cursor_impl._statement._cursor_id)
|
buf.write_ub4(cursor_impl._statement._cursor_id)
|
||||||
elif ora_type_num == TNS_DATA_TYPE_BOOLEAN:
|
elif ora_type_num == TNS_DATA_TYPE_BOOLEAN:
|
||||||
if value:
|
buf.write_bool(value)
|
||||||
buf.write_uint8(2)
|
|
||||||
buf.write_uint16(0x0101)
|
|
||||||
else:
|
|
||||||
buf.write_uint16(0x0100)
|
|
||||||
elif ora_type_num == TNS_DATA_TYPE_INTERVAL_DS:
|
elif ora_type_num == TNS_DATA_TYPE_INTERVAL_DS:
|
||||||
buf.write_interval_ds(value)
|
buf.write_interval_ds(value)
|
||||||
elif ora_type_num == TNS_DATA_TYPE_CLOB \
|
elif ora_type_num == TNS_DATA_TYPE_CLOB \
|
||||||
or ora_type_num == TNS_DATA_TYPE_BLOB:
|
or ora_type_num == TNS_DATA_TYPE_BLOB:
|
||||||
lob_impl = value._impl
|
buf.write_lob_with_length(value._impl)
|
||||||
num_bytes = <uint32_t> len(lob_impl._locator)
|
|
||||||
buf.write_ub4(num_bytes)
|
|
||||||
buf.write_bytes_chunked(lob_impl._locator)
|
|
||||||
elif ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
|
elif ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
|
||||||
temp_bytes = (<str> value).encode()
|
temp_bytes = (<str> value).encode()
|
||||||
buf.write_bytes_chunked(temp_bytes)
|
buf.write_bytes_with_length(temp_bytes)
|
||||||
|
elif ora_type_num == TNS_DATA_TYPE_INT_NAMED:
|
||||||
|
buf.write_dbobject(value._impl)
|
||||||
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)
|
||||||
|
@ -1461,10 +1493,10 @@ cdef class AuthMessage(Message):
|
||||||
uint32_t key_len = <uint32_t> len(key_bytes)
|
uint32_t key_len = <uint32_t> len(key_bytes)
|
||||||
uint32_t value_len = <uint32_t> len(value_bytes)
|
uint32_t value_len = <uint32_t> len(value_bytes)
|
||||||
buf.write_ub4(key_len)
|
buf.write_ub4(key_len)
|
||||||
buf.write_bytes_chunked(key_bytes)
|
buf.write_bytes_with_length(key_bytes)
|
||||||
buf.write_ub4(value_len)
|
buf.write_ub4(value_len)
|
||||||
if value_len > 0:
|
if value_len > 0:
|
||||||
buf.write_bytes_chunked(value_bytes)
|
buf.write_bytes_with_length(value_bytes)
|
||||||
buf.write_ub4(flags)
|
buf.write_ub4(flags)
|
||||||
|
|
||||||
cdef int _write_message(self, WriteBuffer buf) except -1:
|
cdef int _write_message(self, WriteBuffer buf) except -1:
|
||||||
|
@ -2049,7 +2081,7 @@ cdef class LobOpMessage(Message):
|
||||||
buf.write_ub4(TNS_CHARSET_UTF8)
|
buf.write_ub4(TNS_CHARSET_UTF8)
|
||||||
if self.data is not None:
|
if self.data is not None:
|
||||||
buf.write_uint8(TNS_MSG_TYPE_LOB_DATA)
|
buf.write_uint8(TNS_MSG_TYPE_LOB_DATA)
|
||||||
buf.write_bytes_chunked(self.data)
|
buf.write_bytes_with_length(self.data)
|
||||||
if self.send_amount:
|
if self.send_amount:
|
||||||
buf.write_ub8(self.amount) # LOB amount
|
buf.write_ub8(self.amount) # LOB amount
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,656 @@
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2020, 2022, Oracle and/or its affiliates.
|
||||||
|
#
|
||||||
|
# This software is dual-licensed to you under the Universal Permissive License
|
||||||
|
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||||||
|
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||||||
|
# either license.
|
||||||
|
#
|
||||||
|
# If you elect to accept the software under the Apache License, Version 2.0,
|
||||||
|
# the following applies:
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# buffer.pyx
|
||||||
|
#
|
||||||
|
# Cython file defining the low-level network buffer read and write classes and
|
||||||
|
# methods for reading and writing low-level data from those buffers (embedded
|
||||||
|
# in thin_impl.pyx).
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
DEF PACKET_HEADER_SIZE = 8
|
||||||
|
DEF CHUNKED_BYTES_CHUNK_SIZE = 65536
|
||||||
|
|
||||||
|
cdef struct BytesChunk:
|
||||||
|
char_type *ptr
|
||||||
|
uint32_t length
|
||||||
|
uint32_t allocated_length
|
||||||
|
|
||||||
|
cdef struct Rowid:
|
||||||
|
uint32_t rba
|
||||||
|
uint16_t partition_id
|
||||||
|
uint32_t block_num
|
||||||
|
uint16_t slot_num
|
||||||
|
|
||||||
|
@cython.final
|
||||||
|
cdef class ChunkedBytesBuffer:
|
||||||
|
|
||||||
|
cdef:
|
||||||
|
uint32_t _num_chunks
|
||||||
|
uint32_t _allocated_chunks
|
||||||
|
BytesChunk *_chunks
|
||||||
|
|
||||||
|
def __dealloc__(self):
|
||||||
|
cdef uint32_t i
|
||||||
|
for i in range(self._allocated_chunks):
|
||||||
|
if self._chunks[i].ptr is not NULL:
|
||||||
|
cpython.PyMem_Free(self._chunks[i].ptr)
|
||||||
|
self._chunks[i].ptr = NULL
|
||||||
|
if self._chunks is not NULL:
|
||||||
|
cpython.PyMem_Free(self._chunks)
|
||||||
|
self._chunks = NULL
|
||||||
|
|
||||||
|
cdef int _allocate_chunks(self) except -1:
|
||||||
|
"""
|
||||||
|
Allocates a new set of chunks and copies data from the original set of
|
||||||
|
chunks if needed.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
BytesChunk *chunks
|
||||||
|
uint32_t allocated_chunks
|
||||||
|
allocated_chunks = self._allocated_chunks + 8
|
||||||
|
chunks = <BytesChunk*> \
|
||||||
|
cpython.PyMem_Malloc(sizeof(BytesChunk) * allocated_chunks)
|
||||||
|
memset(chunks, 0, sizeof(BytesChunk) * allocated_chunks)
|
||||||
|
if self._num_chunks > 0:
|
||||||
|
memcpy(chunks, self._chunks, sizeof(BytesChunk) * self._num_chunks)
|
||||||
|
cpython.PyMem_Free(self._chunks)
|
||||||
|
self._chunks = chunks
|
||||||
|
self._allocated_chunks = allocated_chunks
|
||||||
|
|
||||||
|
cdef BytesChunk* _get_chunk(self, uint32_t num_bytes) except NULL:
|
||||||
|
"""
|
||||||
|
Return the chunk that can be used to write the number of bytes
|
||||||
|
requested.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint32_t num_allocated_bytes
|
||||||
|
BytesChunk *chunk
|
||||||
|
if self._num_chunks > 0:
|
||||||
|
chunk = &self._chunks[self._num_chunks - 1]
|
||||||
|
if chunk.allocated_length >= chunk.length + num_bytes:
|
||||||
|
return chunk
|
||||||
|
if self._num_chunks >= self._allocated_chunks:
|
||||||
|
self._allocate_chunks()
|
||||||
|
self._num_chunks += 1
|
||||||
|
chunk = &self._chunks[self._num_chunks - 1]
|
||||||
|
chunk.length = 0
|
||||||
|
if chunk.allocated_length < num_bytes:
|
||||||
|
num_allocated_bytes = self._get_chunk_size(num_bytes)
|
||||||
|
if chunk.ptr:
|
||||||
|
cpython.PyMem_Free(chunk.ptr)
|
||||||
|
chunk.ptr = <char_type*> cpython.PyMem_Malloc(num_allocated_bytes)
|
||||||
|
chunk.allocated_length = num_allocated_bytes
|
||||||
|
return chunk
|
||||||
|
|
||||||
|
cdef inline uint32_t _get_chunk_size(self, uint32_t size):
|
||||||
|
"""
|
||||||
|
Returns the size to allocate aligned on a 64K boundary.
|
||||||
|
"""
|
||||||
|
return (size + CHUNKED_BYTES_CHUNK_SIZE - 1) & \
|
||||||
|
~(CHUNKED_BYTES_CHUNK_SIZE - 1)
|
||||||
|
|
||||||
|
cdef char_type* end_chunked_read(self) except NULL:
|
||||||
|
"""
|
||||||
|
Called when a chunked read has ended. Since a chunked read is never
|
||||||
|
started until at least some bytes are being read, it is assumed that at
|
||||||
|
least one chunk is in use. If one chunk is in use, those bytes are
|
||||||
|
returned directly, but if more than one chunk is in use, the first
|
||||||
|
chunk is resized to include all of the bytes in a contiguous section of
|
||||||
|
memory first.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint32_t i, num_allocated_bytes, total_num_bytes = 0, pos = 0
|
||||||
|
char_type *ptr
|
||||||
|
if self._num_chunks > 1:
|
||||||
|
for i in range(self._num_chunks):
|
||||||
|
total_num_bytes += self._chunks[i].length
|
||||||
|
num_allocated_bytes = self._get_chunk_size(total_num_bytes)
|
||||||
|
ptr = <char_type*> cpython.PyMem_Malloc(num_allocated_bytes)
|
||||||
|
for i in range(self._num_chunks):
|
||||||
|
memcpy(&ptr[pos], self._chunks[i].ptr, self._chunks[i].length)
|
||||||
|
pos += self._chunks[i].length
|
||||||
|
cpython.PyMem_Free(self._chunks[i].ptr)
|
||||||
|
self._chunks[i].ptr = NULL
|
||||||
|
self._chunks[i].allocated_length = 0
|
||||||
|
self._chunks[i].length = 0
|
||||||
|
self._num_chunks = 1
|
||||||
|
self._chunks[0].ptr = ptr
|
||||||
|
self._chunks[0].length = total_num_bytes
|
||||||
|
self._chunks[0].allocated_length = num_allocated_bytes
|
||||||
|
return self._chunks[0].ptr
|
||||||
|
|
||||||
|
cdef char_type* get_chunk_ptr(self, uint32_t size_required) except NULL:
|
||||||
|
"""
|
||||||
|
Called when memory is required for a chunked read.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
BytesChunk *chunk
|
||||||
|
char_type *ptr
|
||||||
|
chunk = self._get_chunk(size_required)
|
||||||
|
ptr = &chunk.ptr[chunk.length]
|
||||||
|
chunk.length += size_required
|
||||||
|
return ptr
|
||||||
|
|
||||||
|
cdef inline void start_chunked_read(self):
|
||||||
|
"""
|
||||||
|
Called when a chunked read is started and simply indicates that no
|
||||||
|
chunks are in use. The memory is retained in order to reduce the
|
||||||
|
overhead in freeing and reallocating memory for each chunked read.
|
||||||
|
"""
|
||||||
|
self._num_chunks = 0
|
||||||
|
|
||||||
|
|
||||||
|
@cython.final
|
||||||
|
cdef class ReadBuffer(Buffer):
|
||||||
|
|
||||||
|
cdef:
|
||||||
|
ssize_t _max_packet_size, _bytes_to_process
|
||||||
|
ChunkedBytesBuffer _chunked_bytes_buf
|
||||||
|
bint _session_needs_to_be_closed
|
||||||
|
const char_type _split_data[255]
|
||||||
|
ssize_t _packet_start_offset
|
||||||
|
Capabilities _caps
|
||||||
|
object _socket
|
||||||
|
|
||||||
|
def __cinit__(self, object sock, ssize_t max_packet_size,
|
||||||
|
Capabilities caps):
|
||||||
|
self._socket = sock
|
||||||
|
self._caps = caps
|
||||||
|
self._max_packet_size = max_packet_size
|
||||||
|
self._initialize(max_packet_size * 2)
|
||||||
|
self._chunked_bytes_buf = ChunkedBytesBuffer()
|
||||||
|
|
||||||
|
cdef inline int _get_data_from_socket(self, object obj,
|
||||||
|
ssize_t bytes_requested,
|
||||||
|
ssize_t *bytes_read) except -1:
|
||||||
|
"""
|
||||||
|
Simple function that performs a socket read while verifying that the
|
||||||
|
server has not reset the connection. If it has, the dead connection
|
||||||
|
error is returned instead.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
bytes_read[0] = self._socket.recv_into(obj, bytes_requested)
|
||||||
|
except ConnectionResetError as e:
|
||||||
|
errors._raise_err(errors.ERR_CONNECTION_CLOSED, str(e), cause=e)
|
||||||
|
if bytes_read[0] == 0:
|
||||||
|
try:
|
||||||
|
self._socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
self._socket.close()
|
||||||
|
self._socket = None
|
||||||
|
errors._raise_err(errors.ERR_CONNECTION_CLOSED)
|
||||||
|
|
||||||
|
cdef int _get_int_length_and_sign(self, uint8_t *length,
|
||||||
|
bint *is_negative,
|
||||||
|
uint8_t max_length) except -1:
|
||||||
|
"""
|
||||||
|
Returns the length of an integer sent on the wire. A check is also made
|
||||||
|
to ensure the integer does not exceed the maximum length. If the
|
||||||
|
is_negative pointer is NULL, negative integers will result in an
|
||||||
|
exception being raised.
|
||||||
|
"""
|
||||||
|
cdef const char_type *ptr = self._get_raw(1)
|
||||||
|
if ptr[0] & 0x80:
|
||||||
|
if is_negative == NULL:
|
||||||
|
errors._raise_err(errors.ERR_UNEXPECTED_NEGATIVE_INTEGER)
|
||||||
|
is_negative[0] = True
|
||||||
|
length[0] = ptr[0] & 0x7f
|
||||||
|
else:
|
||||||
|
if is_negative != NULL:
|
||||||
|
is_negative[0] = False
|
||||||
|
length[0] = ptr[0]
|
||||||
|
if length[0] > max_length:
|
||||||
|
errors._raise_err(errors.ERR_INTEGER_TOO_LARGE, length=length[0],
|
||||||
|
max_length=max_length)
|
||||||
|
|
||||||
|
cdef const char_type* _get_more_data(self, ssize_t num_bytes_available,
|
||||||
|
ssize_t num_bytes_wanted) except NULL:
|
||||||
|
"""
|
||||||
|
Called when the amount of data available is less than the amount of
|
||||||
|
data requested. This will fetch another packet from the server. The
|
||||||
|
original data will be saved either in a chunked bytes buffer or the
|
||||||
|
split buffer (depending on how the data was requested).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
cdef const char_type* _get_raw(self, ssize_t num_bytes,
|
||||||
|
bint in_chunked_read=False) except NULL:
|
||||||
|
"""
|
||||||
|
Returns a pointer to a buffer containing the requested number of bytes.
|
||||||
|
This may be split across multiple packets in which case a chunked bytes
|
||||||
|
buffer is used.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ssize_t num_bytes_left, num_bytes_split, max_split_data
|
||||||
|
uint8_t packet_type, packet_flags
|
||||||
|
const char_type *source_ptr
|
||||||
|
char_type *dest_ptr
|
||||||
|
|
||||||
|
# if no bytes are left in the buffer, a new packet needs to be fetched
|
||||||
|
# before anything else can take place
|
||||||
|
if self._pos == self._size:
|
||||||
|
self.receive_packet(&packet_type, &packet_flags)
|
||||||
|
self.skip_raw_bytes(2) # skip data flags
|
||||||
|
|
||||||
|
# if there is enough room in the buffer to satisfy the number of bytes
|
||||||
|
# requested, return a pointer to the current location and advance the
|
||||||
|
# offset the required number of bytes
|
||||||
|
source_ptr = &self._data[self._pos]
|
||||||
|
num_bytes_left = self._size - self._pos
|
||||||
|
if num_bytes <= num_bytes_left:
|
||||||
|
if in_chunked_read:
|
||||||
|
dest_ptr = self._chunked_bytes_buf.get_chunk_ptr(num_bytes)
|
||||||
|
memcpy(dest_ptr, source_ptr, num_bytes)
|
||||||
|
self._pos += num_bytes
|
||||||
|
return source_ptr
|
||||||
|
|
||||||
|
# the requested bytes are split across multiple packets; if a chunked
|
||||||
|
# read is in progress, a chunk is acquired that will accommodate the
|
||||||
|
# remainder of the bytes in the current packet; otherwise, the split
|
||||||
|
# buffer will be used instead (after first checking to see if there is
|
||||||
|
# sufficient room available within it)
|
||||||
|
if in_chunked_read:
|
||||||
|
dest_ptr = self._chunked_bytes_buf.get_chunk_ptr(num_bytes_left)
|
||||||
|
else:
|
||||||
|
max_split_data = sizeof(self._split_data)
|
||||||
|
if max_split_data < num_bytes:
|
||||||
|
errors._raise_err(errors.ERR_BUFFER_LENGTH_INSUFFICIENT,
|
||||||
|
actual_buffer_len=max_split_data,
|
||||||
|
required_buffer_len=num_bytes)
|
||||||
|
dest_ptr = <char_type*> self._split_data
|
||||||
|
memcpy(dest_ptr, source_ptr, num_bytes_left)
|
||||||
|
|
||||||
|
# acquire packets until the requested number of bytes is satisfied
|
||||||
|
num_bytes -= num_bytes_left
|
||||||
|
while num_bytes > 0:
|
||||||
|
|
||||||
|
# acquire new packet
|
||||||
|
self.receive_packet(&packet_type, &packet_flags)
|
||||||
|
self.skip_raw_bytes(2) # skip data flags
|
||||||
|
|
||||||
|
# copy data into the chunked buffer or split buffer, as appropriate
|
||||||
|
source_ptr = &self._data[self._pos]
|
||||||
|
num_bytes_split = min(num_bytes, self._size - self._pos)
|
||||||
|
if in_chunked_read:
|
||||||
|
dest_ptr = \
|
||||||
|
self._chunked_bytes_buf.get_chunk_ptr(num_bytes_split)
|
||||||
|
else:
|
||||||
|
dest_ptr = <char_type*> &self._split_data[num_bytes_left]
|
||||||
|
memcpy(dest_ptr, source_ptr, num_bytes_split)
|
||||||
|
self._pos += num_bytes_split
|
||||||
|
num_bytes -= num_bytes_split
|
||||||
|
|
||||||
|
# return the split buffer unconditionally; if performing a chunked read
|
||||||
|
# the return value is ignored anyway
|
||||||
|
return self._split_data
|
||||||
|
|
||||||
|
cdef int _read_raw_bytes_and_length(self, const char_type **ptr,
|
||||||
|
ssize_t *num_bytes) except -1:
|
||||||
|
"""
|
||||||
|
Helper function that processes the length. If the length is defined as
|
||||||
|
TNS_LONG_LENGTH_INDICATOR, a chunked read is performed.
|
||||||
|
"""
|
||||||
|
cdef uint32_t temp_num_bytes
|
||||||
|
if num_bytes[0] != TNS_LONG_LENGTH_INDICATOR:
|
||||||
|
return Buffer._read_raw_bytes_and_length(self, ptr, num_bytes)
|
||||||
|
self._chunked_bytes_buf.start_chunked_read()
|
||||||
|
num_bytes[0] = 0
|
||||||
|
while True:
|
||||||
|
self.read_ub4(&temp_num_bytes)
|
||||||
|
if temp_num_bytes == 0:
|
||||||
|
break
|
||||||
|
num_bytes[0] += temp_num_bytes
|
||||||
|
self._get_raw(temp_num_bytes, in_chunked_read=True)
|
||||||
|
ptr[0] = self._chunked_bytes_buf.end_chunked_read()
|
||||||
|
|
||||||
|
cdef int _process_control_packet(self) except -1:
|
||||||
|
"""
|
||||||
|
Process a control packed received in between data packets.
|
||||||
|
"""
|
||||||
|
cdef uint16_t control_type, error_num
|
||||||
|
self.read_uint16(&control_type)
|
||||||
|
if control_type == TNS_CONTROL_TYPE_RESET_OOB:
|
||||||
|
self._caps.supports_oob = False
|
||||||
|
elif control_type == TNS_CONTROL_TYPE_INBAND_NOTIFICATION:
|
||||||
|
self.skip_raw_bytes(6) # skip flags
|
||||||
|
self.read_uint16(&error_num)
|
||||||
|
self.skip_raw_bytes(4)
|
||||||
|
if error_num == TNS_ERR_SESSION_SHUTDOWN \
|
||||||
|
or error_num == TNS_ERR_INBAND_MESSAGE:
|
||||||
|
self._session_needs_to_be_closed = True
|
||||||
|
else:
|
||||||
|
errors._raise_err(errors.ERR_UNSUPPORTED_INBAND_NOTIFICATION,
|
||||||
|
err_num=error_num)
|
||||||
|
|
||||||
|
cdef int _receive_packet_helper(self, uint8_t *packet_type,
|
||||||
|
uint8_t *packet_flags) except -1:
|
||||||
|
"""
|
||||||
|
Receives a packet and updates the pointers appropriately. Note that
|
||||||
|
multiple packets may be received if they are small enough or a portion
|
||||||
|
of a second packet may be received so the buffer needs to be adjusted
|
||||||
|
as needed. This is also why room is available in the buffer for up to
|
||||||
|
two complete packets.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ssize_t offset, bytes_to_read, bytes_read
|
||||||
|
uint32_t packet_size
|
||||||
|
uint16_t temp16
|
||||||
|
|
||||||
|
# if no bytes are left over from a previous read, perform a read of the
|
||||||
|
# maximum packet size and reset the offset to 0
|
||||||
|
if self._bytes_to_process == 0:
|
||||||
|
self._packet_start_offset = 0
|
||||||
|
self._pos = 0
|
||||||
|
self._get_data_from_socket(self._data_obj, self._max_packet_size,
|
||||||
|
&self._bytes_to_process)
|
||||||
|
|
||||||
|
# otherwise, set the offset to the end of the previous packet and
|
||||||
|
# ensure that there are at least enough bytes available to cover the
|
||||||
|
# contents of the packet header
|
||||||
|
else:
|
||||||
|
self._packet_start_offset = self._size
|
||||||
|
self._pos = self._size
|
||||||
|
if self._bytes_to_process < PACKET_HEADER_SIZE:
|
||||||
|
offset = self._size + self._bytes_to_process
|
||||||
|
bytes_to_read = PACKET_HEADER_SIZE - self._bytes_to_process
|
||||||
|
self._get_data_from_socket(self._data_view[offset:],
|
||||||
|
bytes_to_read, &bytes_read)
|
||||||
|
self._bytes_to_process += bytes_read
|
||||||
|
|
||||||
|
# determine the packet length and ensure that all of the bytes for the
|
||||||
|
# packet are available; note that as of version 12.2 the packet size is
|
||||||
|
# 32 bits in size instead of 16 bits, but this doesn't take effect
|
||||||
|
# until it is known that the server is capable of that as well
|
||||||
|
if self._caps.protocol_version >= TNS_VERSION_MIN_LARGE_SDU:
|
||||||
|
self._size += 4
|
||||||
|
self.read_uint32(&packet_size)
|
||||||
|
else:
|
||||||
|
self._size += 2
|
||||||
|
self.read_uint16(&temp16)
|
||||||
|
packet_size = temp16
|
||||||
|
while self._bytes_to_process < packet_size:
|
||||||
|
offset = self._packet_start_offset + self._bytes_to_process
|
||||||
|
bytes_to_read = packet_size - self._bytes_to_process
|
||||||
|
self._get_data_from_socket(self._data_view[offset:], bytes_to_read,
|
||||||
|
&bytes_read)
|
||||||
|
self._bytes_to_process += bytes_read
|
||||||
|
|
||||||
|
# process remainder of packet header and set size to the new packet
|
||||||
|
self._size = self._packet_start_offset + packet_size
|
||||||
|
self._bytes_to_process -= packet_size
|
||||||
|
if self._caps.protocol_version < TNS_VERSION_MIN_LARGE_SDU:
|
||||||
|
self.skip_raw_bytes(2) # skip packet checksum
|
||||||
|
self.read_ub1(packet_type)
|
||||||
|
self.read_ub1(packet_flags)
|
||||||
|
self.skip_raw_bytes(2) # header checksum
|
||||||
|
|
||||||
|
# display packet if requested
|
||||||
|
if DEBUG_PACKETS:
|
||||||
|
offset = self._packet_start_offset
|
||||||
|
_print_packet("Receiving packet:", self._socket.fileno(),
|
||||||
|
self._data_view[offset:self._size])
|
||||||
|
|
||||||
|
cdef object read_lob_with_length(self, ThinConnImpl conn_impl,
|
||||||
|
DbType dbtype):
|
||||||
|
"""
|
||||||
|
Read a LOB locator from the buffer and return a LOB object containing
|
||||||
|
it.
|
||||||
|
"""
|
||||||
|
cdef uint32_t num_bytes
|
||||||
|
self.read_ub4(&num_bytes)
|
||||||
|
if num_bytes > 0:
|
||||||
|
return self.read_lob(conn_impl, dbtype)
|
||||||
|
|
||||||
|
cdef int read_rowid(self, Rowid *rowid) except -1:
|
||||||
|
"""
|
||||||
|
Reads a rowid from the buffer and populates the rowid structure.
|
||||||
|
"""
|
||||||
|
self.read_ub4(&rowid.rba)
|
||||||
|
self.read_ub2(&rowid.partition_id)
|
||||||
|
self.skip_ub1()
|
||||||
|
self.read_ub4(&rowid.block_num)
|
||||||
|
self.read_ub2(&rowid.slot_num)
|
||||||
|
|
||||||
|
cdef object read_urowid(self):
|
||||||
|
"""
|
||||||
|
Read a universal rowid from the buffer and return the Python object
|
||||||
|
representing its value.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
ssize_t output_len, input_len, remainder, pos
|
||||||
|
int input_offset = 1, output_offset = 0
|
||||||
|
const char_type *input_ptr
|
||||||
|
bytearray output_value
|
||||||
|
uint32_t num_bytes
|
||||||
|
uint8_t length
|
||||||
|
Rowid rowid
|
||||||
|
|
||||||
|
# get data (first buffer contains the length, which can be ignored)
|
||||||
|
self.read_raw_bytes_and_length(&input_ptr, &input_len)
|
||||||
|
if input_ptr == NULL:
|
||||||
|
return None
|
||||||
|
self.read_raw_bytes_and_length(&input_ptr, &input_len)
|
||||||
|
|
||||||
|
# handle physical rowid
|
||||||
|
if input_ptr[0] == 1:
|
||||||
|
rowid.rba = unpack_uint32(&input_ptr[1], BYTE_ORDER_MSB)
|
||||||
|
rowid.partition_id = unpack_uint16(&input_ptr[5], BYTE_ORDER_MSB)
|
||||||
|
rowid.block_num = unpack_uint32(&input_ptr[7], BYTE_ORDER_MSB)
|
||||||
|
rowid.slot_num = unpack_uint16(&input_ptr[11], BYTE_ORDER_MSB)
|
||||||
|
return _encode_rowid(&rowid)
|
||||||
|
|
||||||
|
# handle logical rowid
|
||||||
|
output_len = (input_len // 3) * 4
|
||||||
|
remainder = input_len % 3
|
||||||
|
if remainder == 1:
|
||||||
|
output_len += 1
|
||||||
|
elif remainder == 2:
|
||||||
|
output_len += 3
|
||||||
|
output_value = bytearray(output_len)
|
||||||
|
input_len -= 1
|
||||||
|
output_value[0] = 42 # '*'
|
||||||
|
output_offset += 1
|
||||||
|
while input_len > 0:
|
||||||
|
|
||||||
|
# produce first byte of quadruple
|
||||||
|
pos = input_ptr[input_offset] >> 2
|
||||||
|
output_value[output_offset] = TNS_BASE64_ALPHABET_ARRAY[pos]
|
||||||
|
output_offset += 1
|
||||||
|
|
||||||
|
# produce second byte of quadruple, but if only one byte is left,
|
||||||
|
# produce that one byte and exit
|
||||||
|
pos = (input_ptr[input_offset] & 0x3) << 4
|
||||||
|
if input_len == 1:
|
||||||
|
output_value[output_offset] = TNS_BASE64_ALPHABET_ARRAY[pos]
|
||||||
|
break
|
||||||
|
input_offset += 1
|
||||||
|
pos |= ((input_ptr[input_offset] & 0xf0) >> 4)
|
||||||
|
output_value[output_offset] = TNS_BASE64_ALPHABET_ARRAY[pos]
|
||||||
|
output_offset += 1
|
||||||
|
|
||||||
|
# produce third byte of quadruple, but if only two bytes are left,
|
||||||
|
# produce that one byte and exit
|
||||||
|
pos = (input_ptr[input_offset] & 0xf) << 2
|
||||||
|
if input_len == 2:
|
||||||
|
output_value[output_offset] = TNS_BASE64_ALPHABET_ARRAY[pos]
|
||||||
|
break
|
||||||
|
input_offset += 1
|
||||||
|
pos |= ((input_ptr[input_offset] & 0xc0) >> 6)
|
||||||
|
output_value[output_offset] = TNS_BASE64_ALPHABET_ARRAY[pos]
|
||||||
|
output_offset += 1
|
||||||
|
|
||||||
|
# produce final byte of quadruple
|
||||||
|
pos = input_ptr[input_offset] & 0x3f
|
||||||
|
output_value[output_offset] = TNS_BASE64_ALPHABET_ARRAY[pos]
|
||||||
|
output_offset += 1
|
||||||
|
input_offset += 1
|
||||||
|
input_len -= 3
|
||||||
|
|
||||||
|
return bytes(output_value).decode()
|
||||||
|
|
||||||
|
cdef int check_control_packet(self) except -1:
|
||||||
|
"""
|
||||||
|
Checks for a control packet or final close packet from the server.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint8_t packet_type, packet_flags
|
||||||
|
uint16_t data_flags
|
||||||
|
self._receive_packet_helper(&packet_type, &packet_flags)
|
||||||
|
if packet_type == TNS_PACKET_TYPE_CONTROL:
|
||||||
|
self._process_control_packet()
|
||||||
|
elif packet_type == TNS_PACKET_TYPE_DATA:
|
||||||
|
self.read_uint16(&data_flags)
|
||||||
|
if data_flags == TNS_DATA_FLAGS_EOF:
|
||||||
|
self._session_needs_to_be_closed = True
|
||||||
|
|
||||||
|
cdef int receive_packet(self, uint8_t *packet_type,
|
||||||
|
uint8_t *packet_flags) except -1:
|
||||||
|
"""
|
||||||
|
Calls _receive_packet_helper() and checks the packet type. If a
|
||||||
|
control packet is received, it is processed and the next packet is
|
||||||
|
received.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
self._receive_packet_helper(packet_type, packet_flags)
|
||||||
|
if packet_type[0] == TNS_PACKET_TYPE_CONTROL:
|
||||||
|
self._process_control_packet()
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
|
||||||
|
cdef int skip_raw_bytes_chunked(self) except -1:
|
||||||
|
"""
|
||||||
|
Skip a number of bytes that may or may not be chunked in the buffer.
|
||||||
|
The first byte gives the length. If the length is
|
||||||
|
TNS_LONG_LENGTH_INDICATOR, however, chunks are read and discarded.
|
||||||
|
"""
|
||||||
|
cdef:
|
||||||
|
uint32_t temp_num_bytes
|
||||||
|
uint8_t length
|
||||||
|
self.read_ub1(&length)
|
||||||
|
if length != TNS_LONG_LENGTH_INDICATOR:
|
||||||
|
self.skip_raw_bytes(length)
|
||||||
|
else:
|
||||||
|
while True:
|
||||||
|
self.read_ub4(&temp_num_bytes)
|
||||||
|
if temp_num_bytes == 0:
|
||||||
|
break
|
||||||
|
self.skip_raw_bytes(temp_num_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
@cython.final
|
||||||
|
cdef class WriteBuffer(Buffer):
|
||||||
|
|
||||||
|
cdef:
|
||||||
|
uint8_t _packet_type
|
||||||
|
Capabilities _caps
|
||||||
|
object _socket
|
||||||
|
uint8_t _seq_num
|
||||||
|
bint _packet_sent
|
||||||
|
|
||||||
|
def __cinit__(self, object sock, ssize_t max_size, Capabilities caps):
|
||||||
|
self._socket = sock
|
||||||
|
self._caps = caps
|
||||||
|
self._initialize(max_size)
|
||||||
|
|
||||||
|
cdef int _send_packet(self, bint final_packet) except -1:
|
||||||
|
"""
|
||||||
|
Write the packet header and then send the packet. Once sent, reset the
|
||||||
|
pointers back to an empty packet.
|
||||||
|
"""
|
||||||
|
cdef ssize_t size = self._pos
|
||||||
|
self._pos = 0
|
||||||
|
if self._caps.protocol_version >= TNS_VERSION_MIN_LARGE_SDU:
|
||||||
|
self.write_uint32(size)
|
||||||
|
else:
|
||||||
|
self.write_uint16(size)
|
||||||
|
self.write_uint16(0)
|
||||||
|
self.write_uint8(self._packet_type)
|
||||||
|
self.write_uint8(0)
|
||||||
|
self.write_uint16(0)
|
||||||
|
self._pos = size
|
||||||
|
if DEBUG_PACKETS:
|
||||||
|
_print_packet("Sending packet:", self._socket.fileno(),
|
||||||
|
self._data_view[:self._pos])
|
||||||
|
try:
|
||||||
|
self._socket.send(self._data_view[:self._pos])
|
||||||
|
except OSError as e:
|
||||||
|
errors._raise_err(errors.ERR_CONNECTION_CLOSED, str(e))
|
||||||
|
self._packet_sent = True
|
||||||
|
self._pos = PACKET_HEADER_SIZE
|
||||||
|
if not final_packet:
|
||||||
|
self.write_uint16(0) # add data flags for next packet
|
||||||
|
|
||||||
|
cdef int _write_more_data(self, ssize_t num_bytes_available,
|
||||||
|
ssize_t num_bytes_wanted) except -1:
|
||||||
|
"""
|
||||||
|
Called when the amount of buffer available is less than the amount of
|
||||||
|
data requested. This sends the packet to the server and then resets the
|
||||||
|
buffer for further writing.
|
||||||
|
"""
|
||||||
|
self._send_packet(final_packet=False)
|
||||||
|
|
||||||
|
cdef int end_request(self) except -1:
|
||||||
|
"""
|
||||||
|
Indicates that the request from the client is completing and will send
|
||||||
|
any packet remaining, if necessary.
|
||||||
|
"""
|
||||||
|
if self._pos > PACKET_HEADER_SIZE:
|
||||||
|
self._send_packet(final_packet=True)
|
||||||
|
|
||||||
|
cdef inline ssize_t max_payload_bytes(self):
|
||||||
|
"""
|
||||||
|
Return the maximum number of bytes that can be sent in a packet. This
|
||||||
|
is the maximum size of the entire packet, less the bytes in the header
|
||||||
|
and 2 bytes for the data flags.
|
||||||
|
"""
|
||||||
|
return self._max_size - PACKET_HEADER_SIZE - 2
|
||||||
|
|
||||||
|
cdef void start_request(self, uint8_t packet_type, uint16_t data_flags=0):
|
||||||
|
"""
|
||||||
|
Indicates that a request from the client is starting. The packet type
|
||||||
|
is retained just in case a request spans multiple packets. The packet
|
||||||
|
header (8 bytes in length) is written when a packet is actually being
|
||||||
|
sent and so is skipped at this point.
|
||||||
|
"""
|
||||||
|
self._packet_sent = False
|
||||||
|
self._packet_type = packet_type
|
||||||
|
self._pos = PACKET_HEADER_SIZE
|
||||||
|
if packet_type == TNS_PACKET_TYPE_DATA:
|
||||||
|
self.write_uint16(data_flags)
|
||||||
|
|
||||||
|
cdef int write_lob_with_length(self, ThinLobImpl lob_impl) except -1:
|
||||||
|
"""
|
||||||
|
Writes a LOB locator to the buffer.
|
||||||
|
"""
|
||||||
|
self.write_ub4(len(lob_impl._locator))
|
||||||
|
return self.write_lob(lob_impl)
|
||||||
|
|
||||||
|
cdef int write_seq_num(self) except -1:
|
||||||
|
self._seq_num += 1
|
||||||
|
if self._seq_num == 0:
|
||||||
|
self._seq_num = 1
|
||||||
|
self.write_uint8(self._seq_num)
|
|
@ -145,5 +145,6 @@ def init_thin_impl(package):
|
||||||
Initializes globals after the package has been completely initialized. This
|
Initializes globals after the package has been completely initialized. This
|
||||||
is to avoid circular imports and eliminate the need for global lookups.
|
is to avoid circular imports and eliminate the need for global lookups.
|
||||||
"""
|
"""
|
||||||
global PY_TYPE_LOB
|
global PY_TYPE_DB_OBJECT, PY_TYPE_LOB
|
||||||
|
PY_TYPE_DB_OBJECT = package.DbObject
|
||||||
PY_TYPE_LOB = package.LOB
|
PY_TYPE_LOB = package.LOB
|
||||||
|
|
|
@ -78,12 +78,15 @@ from .base_impl cimport NUM_TYPE_INT, NUM_TYPE_DECIMAL, NUM_TYPE_STR
|
||||||
from .base_impl cimport BaseConnImpl, BaseCursorImpl, BaseVarImpl, DbType
|
from .base_impl cimport BaseConnImpl, BaseCursorImpl, BaseVarImpl, DbType
|
||||||
from .base_impl cimport BaseLobImpl, BasePoolImpl, FetchInfo
|
from .base_impl cimport BaseLobImpl, BasePoolImpl, FetchInfo
|
||||||
from .base_impl cimport Address, AddressList, Description, DescriptionList
|
from .base_impl cimport Address, AddressList, Description, DescriptionList
|
||||||
from .base_impl cimport ConnectParamsImpl, PoolParamsImpl
|
from .base_impl cimport ConnectParamsImpl, PoolParamsImpl, BaseDbObjectAttrImpl
|
||||||
|
from .base_impl cimport BaseDbObjectImpl, BaseDbObjectTypeImpl
|
||||||
from .base_impl import DB_TYPE_BLOB, DB_TYPE_CLOB, DB_TYPE_NCLOB
|
from .base_impl import DB_TYPE_BLOB, DB_TYPE_CLOB, DB_TYPE_NCLOB
|
||||||
|
from .base_impl import DB_TYPE_BINARY_INTEGER, DB_TYPE_CURSOR, DB_TYPE_OBJECT
|
||||||
|
|
||||||
ctypedef unsigned char char_type
|
ctypedef unsigned char char_type
|
||||||
|
|
||||||
cdef type PY_TYPE_DECIMAL = decimal.Decimal
|
cdef type PY_TYPE_DECIMAL = decimal.Decimal
|
||||||
|
cdef type PY_TYPE_DB_OBJECT
|
||||||
cdef type PY_TYPE_LOB
|
cdef type PY_TYPE_LOB
|
||||||
|
|
||||||
cdef bint HAS_CRYPTOGRAPHY = True
|
cdef bint HAS_CRYPTOGRAPHY = True
|
||||||
|
@ -93,6 +96,7 @@ include "impl/thin/utils.pyx"
|
||||||
include "impl/thin/crypto.pyx"
|
include "impl/thin/crypto.pyx"
|
||||||
include "impl/thin/capabilities.pyx"
|
include "impl/thin/capabilities.pyx"
|
||||||
include "impl/thin/buffer.pyx"
|
include "impl/thin/buffer.pyx"
|
||||||
|
include "impl/thin/packet.pyx"
|
||||||
include "impl/thin/network_services.pyx"
|
include "impl/thin/network_services.pyx"
|
||||||
include "impl/thin/data_types.pyx"
|
include "impl/thin/data_types.pyx"
|
||||||
include "impl/thin/messages.pyx"
|
include "impl/thin/messages.pyx"
|
||||||
|
@ -101,6 +105,7 @@ include "impl/thin/connection.pyx"
|
||||||
include "impl/thin/statement.pyx"
|
include "impl/thin/statement.pyx"
|
||||||
include "impl/thin/cursor.pyx"
|
include "impl/thin/cursor.pyx"
|
||||||
include "impl/thin/var.pyx"
|
include "impl/thin/var.pyx"
|
||||||
|
include "impl/thin/dbobject.pyx"
|
||||||
include "impl/thin/lob.pyx"
|
include "impl/thin/lob.pyx"
|
||||||
include "impl/thin/pool.pyx"
|
include "impl/thin/pool.pyx"
|
||||||
include "impl/thin/conversions.pyx"
|
include "impl/thin/conversions.pyx"
|
||||||
|
|
|
@ -202,8 +202,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
]
|
]
|
||||||
self.assertEqual(str_var.values, expected_values)
|
self.assertEqual(str_var.values, expected_values)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_1607_insert_and_return_object(self):
|
def test_1607_insert_and_return_object(self):
|
||||||
"1607 - test inserting an object with DML returning"
|
"1607 - test inserting an object with DML returning"
|
||||||
type_obj = self.connection.gettype("UDT_OBJECT")
|
type_obj = self.connection.gettype("UDT_OBJECT")
|
||||||
|
|
|
@ -33,8 +33,6 @@ import unittest
|
||||||
import oracledb
|
import oracledb
|
||||||
import test_env
|
import test_env
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
class TestCase(test_env.BaseTestCase):
|
class TestCase(test_env.BaseTestCase):
|
||||||
|
|
||||||
def __test_data(self, expected_int_value, expected_obj_value,
|
def __test_data(self, expected_int_value, expected_obj_value,
|
||||||
|
@ -309,7 +307,7 @@ class TestCase(test_env.BaseTestCase):
|
||||||
sub_obj.SUBSTRINGVALUE = "Substring value"
|
sub_obj.SUBSTRINGVALUE = "Substring value"
|
||||||
obj.SUBOBJECTVALUE = sub_obj
|
obj.SUBOBJECTVALUE = sub_obj
|
||||||
self.cursor.execute("insert into TestObjects (IntCol, ObjectCol) " \
|
self.cursor.execute("insert into TestObjects (IntCol, ObjectCol) " \
|
||||||
"values (4, :obj)", obj = obj)
|
"values (4, :obj)", obj=obj)
|
||||||
self.cursor.execute("""
|
self.cursor.execute("""
|
||||||
select IntCol, ObjectCol, ArrayCol
|
select IntCol, ObjectCol, ArrayCol
|
||||||
from TestObjects
|
from TestObjects
|
||||||
|
@ -377,7 +375,7 @@ class TestCase(test_env.BaseTestCase):
|
||||||
|
|
||||||
def test_2308_invalid_type_object(self):
|
def test_2308_invalid_type_object(self):
|
||||||
"2308 - test trying to find an object type that does not exist"
|
"2308 - test trying to find an object type that does not exist"
|
||||||
self.assertRaisesRegex(oracledb.DatabaseError, "^OCI-22303",
|
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-2035:",
|
||||||
self.connection.gettype,
|
self.connection.gettype,
|
||||||
"A TYPE THAT DOES NOT EXIST")
|
"A TYPE THAT DOES NOT EXIST")
|
||||||
|
|
||||||
|
@ -387,7 +385,7 @@ class TestCase(test_env.BaseTestCase):
|
||||||
collection_obj = collection_obj_type.newobject()
|
collection_obj = collection_obj_type.newobject()
|
||||||
array_obj_type = self.connection.gettype("UDT_ARRAY")
|
array_obj_type = self.connection.gettype("UDT_ARRAY")
|
||||||
array_obj = array_obj_type.newobject()
|
array_obj = array_obj_type.newobject()
|
||||||
self.assertRaisesRegex(oracledb.DatabaseError, "^DPI-1056:",
|
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-2008:",
|
||||||
collection_obj.append, array_obj)
|
collection_obj.append, array_obj)
|
||||||
|
|
||||||
def test_2310_referencing_sub_obj(self):
|
def test_2310_referencing_sub_obj(self):
|
||||||
|
@ -425,7 +423,7 @@ class TestCase(test_env.BaseTestCase):
|
||||||
obj = obj_type.newobject()
|
obj = obj_type.newobject()
|
||||||
wrong_obj_type = self.connection.gettype("UDT_OBJECTARRAY")
|
wrong_obj_type = self.connection.gettype("UDT_OBJECTARRAY")
|
||||||
wrong_obj = wrong_obj_type.newobject()
|
wrong_obj = wrong_obj_type.newobject()
|
||||||
self.assertRaisesRegex(oracledb.DatabaseError, "^DPI-1056:", setattr,
|
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-2008:", setattr,
|
||||||
obj, "SUBOBJECTVALUE", wrong_obj)
|
obj, "SUBOBJECTVALUE", wrong_obj)
|
||||||
|
|
||||||
def test_2313_setting_var_wrong_object_type(self):
|
def test_2313_setting_var_wrong_object_type(self):
|
||||||
|
@ -449,7 +447,7 @@ class TestCase(test_env.BaseTestCase):
|
||||||
"2315 - test Trim number of elements from collection"
|
"2315 - test Trim number of elements from collection"
|
||||||
sub_obj_type = self.connection.gettype("UDT_SUBOBJECT")
|
sub_obj_type = self.connection.gettype("UDT_SUBOBJECT")
|
||||||
array_type = self.connection.gettype("UDT_OBJECTARRAY")
|
array_type = self.connection.gettype("UDT_OBJECTARRAY")
|
||||||
data = [(1, "AB"), (2, "CDE"), (3, "FGH"), (4, "IJK")]
|
data = [(1, "AB"), (2, "CDE"), (3, "FGH"), (4, "IJK"), (5, "LMN")]
|
||||||
array_obj = array_type()
|
array_obj = array_type()
|
||||||
for num_val, str_val in data:
|
for num_val, str_val in data:
|
||||||
subObj = sub_obj_type()
|
subObj = sub_obj_type()
|
||||||
|
@ -459,14 +457,14 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self.assertEqual(self.get_db_object_as_plain_object(array_obj), data)
|
self.assertEqual(self.get_db_object_as_plain_object(array_obj), data)
|
||||||
array_obj.trim(2)
|
array_obj.trim(2)
|
||||||
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
|
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
|
||||||
data[:2])
|
data[:3])
|
||||||
array_obj.trim(1)
|
array_obj.trim(1)
|
||||||
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
|
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
|
||||||
data[:1])
|
data[:2])
|
||||||
array_obj.trim(0)
|
array_obj.trim(0)
|
||||||
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
|
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
|
||||||
data[:1])
|
data[:2])
|
||||||
array_obj.trim(1)
|
array_obj.trim(2)
|
||||||
self.assertEqual(self.get_db_object_as_plain_object(array_obj), [])
|
self.assertEqual(self.get_db_object_as_plain_object(array_obj), [])
|
||||||
|
|
||||||
def test_2316_sql_type_metadata(self):
|
def test_2316_sql_type_metadata(self):
|
||||||
|
|
|
@ -68,8 +68,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
count, = self.cursor.fetchone()
|
count, = self.cursor.fetchone()
|
||||||
self.assertEqual(count, len(rows))
|
self.assertEqual(count, len(rows))
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3202_bind_plsql_boolean_collection_in(self):
|
def test_3202_bind_plsql_boolean_collection_in(self):
|
||||||
"3202 - test binding a boolean collection (in)"
|
"3202 - test binding a boolean collection (in)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTBOOLEANS.UDT_BOOLEANLIST")
|
type_obj = self.connection.gettype("PKG_TESTBOOLEANS.UDT_BOOLEANLIST")
|
||||||
|
@ -80,8 +78,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
(obj,))
|
(obj,))
|
||||||
self.assertEqual(result, 5)
|
self.assertEqual(result, 5)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3203_bind_plsql_boolean_collection_out(self):
|
def test_3203_bind_plsql_boolean_collection_out(self):
|
||||||
"3203 - test binding a boolean collection (out)"
|
"3203 - test binding a boolean collection (out)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTBOOLEANS.UDT_BOOLEANLIST")
|
type_obj = self.connection.gettype("PKG_TESTBOOLEANS.UDT_BOOLEANLIST")
|
||||||
|
@ -89,8 +85,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self.cursor.callproc("pkg_TestBooleans.TestOutArrays", (6, obj))
|
self.cursor.callproc("pkg_TestBooleans.TestOutArrays", (6, obj))
|
||||||
self.assertEqual(obj.aslist(), [True, False, True, False, True, False])
|
self.assertEqual(obj.aslist(), [True, False, True, False, True, False])
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3204_bind_plql_date_collection_in(self):
|
def test_3204_bind_plql_date_collection_in(self):
|
||||||
"3204 - test binding a PL/SQL date collection (in)"
|
"3204 - test binding a PL/SQL date collection (in)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
|
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
|
||||||
|
@ -103,8 +97,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
(2, datetime.datetime(2016, 2, 1), obj))
|
(2, datetime.datetime(2016, 2, 1), obj))
|
||||||
self.assertEqual(result, 24.75)
|
self.assertEqual(result, 24.75)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3205_bind_plqsl_date_collection_in_out(self):
|
def test_3205_bind_plqsl_date_collection_in_out(self):
|
||||||
"3205 - test binding a PL/SQL date collection (in/out)"
|
"3205 - test binding a PL/SQL date collection (in/out)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
|
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
|
||||||
|
@ -122,8 +114,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
]
|
]
|
||||||
self.assertEqual(obj.aslist(), expected_values)
|
self.assertEqual(obj.aslist(), expected_values)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3206_bind_plsql_date_collection_out(self):
|
def test_3206_bind_plsql_date_collection_out(self):
|
||||||
"3206 - test binding a PL/SQL date collection (out)"
|
"3206 - test binding a PL/SQL date collection (out)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
|
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
|
||||||
|
@ -136,8 +126,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
]
|
]
|
||||||
self.assertEqual(obj.aslist(), expected_values)
|
self.assertEqual(obj.aslist(), expected_values)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3207_bind_plsql_number_collection_in(self):
|
def test_3207_bind_plsql_number_collection_in(self):
|
||||||
"3207 - test binding a PL/SQL number collection (in)"
|
"3207 - test binding a PL/SQL number collection (in)"
|
||||||
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
|
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
|
||||||
|
@ -149,8 +137,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
(5, obj))
|
(5, obj))
|
||||||
self.assertEqual(result, 155)
|
self.assertEqual(result, 155)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3208_bind_plsql_number_collection_in_out(self):
|
def test_3208_bind_plsql_number_collection_in_out(self):
|
||||||
"3208 - test binding a PL/SQL number collection (in/out)"
|
"3208 - test binding a PL/SQL number collection (in/out)"
|
||||||
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
|
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
|
||||||
|
@ -161,8 +147,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self.cursor.callproc("pkg_TestNumberArrays.TestInOutArrays", (4, obj))
|
self.cursor.callproc("pkg_TestNumberArrays.TestInOutArrays", (4, obj))
|
||||||
self.assertEqual(obj.aslist(), [50, 80, 30, 20])
|
self.assertEqual(obj.aslist(), [50, 80, 30, 20])
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3209_bind_plsql_number_collection_out(self):
|
def test_3209_bind_plsql_number_collection_out(self):
|
||||||
"3209 - test binding a PL/SQL number collection (out)"
|
"3209 - test binding a PL/SQL number collection (out)"
|
||||||
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
|
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
|
||||||
|
@ -171,8 +155,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self.cursor.callproc("pkg_TestNumberArrays.TestOutArrays", (3, obj))
|
self.cursor.callproc("pkg_TestNumberArrays.TestOutArrays", (3, obj))
|
||||||
self.assertEqual(obj.aslist(), [100, 200, 300])
|
self.assertEqual(obj.aslist(), [100, 200, 300])
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3210_bind_plsql_record_array(self):
|
def test_3210_bind_plsql_record_array(self):
|
||||||
"3210 - test binding an array of PL/SQL records (in)"
|
"3210 - test binding an array of PL/SQL records (in)"
|
||||||
rec_type = self.connection.gettype("PKG_TESTRECORDS.UDT_RECORD")
|
rec_type = self.connection.gettype("PKG_TESTRECORDS.UDT_RECORD")
|
||||||
|
@ -204,8 +186,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
"to_timestamp('2017-01-03 00:00:00', " \
|
"to_timestamp('2017-01-03 00:00:00', " \
|
||||||
"'YYYY-MM-DD HH24:MI:SS'), false, 10, 4)")
|
"'YYYY-MM-DD HH24:MI:SS'), false, 10, 4)")
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3211_bind_plsql_record_in(self):
|
def test_3211_bind_plsql_record_in(self):
|
||||||
"3211 - test binding a PL/SQL record (in)"
|
"3211 - test binding a PL/SQL record (in)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTRECORDS.UDT_RECORD")
|
type_obj = self.connection.gettype("PKG_TESTRECORDS.UDT_RECORD")
|
||||||
|
@ -225,8 +205,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
"to_timestamp('2016-02-12 14:25:36', " \
|
"to_timestamp('2016-02-12 14:25:36', " \
|
||||||
"'YYYY-MM-DD HH24:MI:SS'), false, 21, 5)")
|
"'YYYY-MM-DD HH24:MI:SS'), false, 21, 5)")
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3212_bind_plsql_record_out(self):
|
def test_3212_bind_plsql_record_out(self):
|
||||||
"3212 - test binding a PL/SQL record (out)"
|
"3212 - test binding a PL/SQL record (out)"
|
||||||
type_obj = self.connection.gettype("PKG_TESTRECORDS.UDT_RECORD")
|
type_obj = self.connection.gettype("PKG_TESTRECORDS.UDT_RECORD")
|
||||||
|
@ -248,8 +226,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self.assertEqual(obj.PLSINTEGERVALUE, 45)
|
self.assertEqual(obj.PLSINTEGERVALUE, 45)
|
||||||
self.assertEqual(obj.BINARYINTEGERVALUE, 10)
|
self.assertEqual(obj.BINARYINTEGERVALUE, 10)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3213_bind_plsql_string_collection_in(self):
|
def test_3213_bind_plsql_string_collection_in(self):
|
||||||
"3213 - test binding a PL/SQL string collection (in)"
|
"3213 - test binding a PL/SQL string collection (in)"
|
||||||
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
||||||
|
@ -262,8 +238,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
(5, obj))
|
(5, obj))
|
||||||
self.assertEqual(result, 45)
|
self.assertEqual(result, 45)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3214_bind_plsql_string_collection_in_out(self):
|
def test_3214_bind_plsql_string_collection_in_out(self):
|
||||||
"3214 - test binding a PL/SQL string collection (in/out)"
|
"3214 - test binding a PL/SQL string collection (in/out)"
|
||||||
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
||||||
|
@ -280,8 +254,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
]
|
]
|
||||||
self.assertEqual(obj.aslist(), expected_values)
|
self.assertEqual(obj.aslist(), expected_values)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3215_bind_plsql_string_collection_out(self):
|
def test_3215_bind_plsql_string_collection_out(self):
|
||||||
"3215 - test binding a PL/SQL string collection (out)"
|
"3215 - test binding a PL/SQL string collection (out)"
|
||||||
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
||||||
|
@ -296,8 +268,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
]
|
]
|
||||||
self.assertEqual(obj.aslist(), expected_values)
|
self.assertEqual(obj.aslist(), expected_values)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3216_bind_plsql_string_collection_out_with_holes(self):
|
def test_3216_bind_plsql_string_collection_out_with_holes(self):
|
||||||
"3216 - test binding a PL/SQL string collection (out with holes)"
|
"3216 - test binding a PL/SQL string collection (out with holes)"
|
||||||
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
|
||||||
|
|
|
@ -42,7 +42,7 @@ class TestCase(test_env.BaseTestCase):
|
||||||
result = var.getvalue()
|
result = var.getvalue()
|
||||||
if isinstance(result, oracledb.LOB):
|
if isinstance(result, oracledb.LOB):
|
||||||
result = result.read()
|
result = result.read()
|
||||||
elif isinstance(result, oracledb.Object):
|
elif isinstance(result, oracledb.DbObject):
|
||||||
result = self.get_db_object_as_plain_object(result)
|
result = self.get_db_object_as_plain_object(result)
|
||||||
if isinstance(expected_value, datetime.date) \
|
if isinstance(expected_value, datetime.date) \
|
||||||
and not isinstance(expected_value, datetime.datetime):
|
and not isinstance(expected_value, datetime.datetime):
|
||||||
|
@ -298,8 +298,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
self._test_negative_set_and_get(oracledb.DB_TYPE_ROWID, 12345)
|
self._test_negative_set_and_get(oracledb.DB_TYPE_ROWID, 12345)
|
||||||
self._test_negative_set_and_get(oracledb.DB_TYPE_ROWID, "523lkhlf")
|
self._test_negative_set_and_get(oracledb.DB_TYPE_ROWID, "523lkhlf")
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_3721_DB_TYPE_OBJECT(self):
|
def test_3721_DB_TYPE_OBJECT(self):
|
||||||
"3721 - setting values on variables of type DB_TYPE_OBJECT"
|
"3721 - setting values on variables of type DB_TYPE_OBJECT"
|
||||||
obj_type = self.connection.gettype("UDT_OBJECT")
|
obj_type = self.connection.gettype("UDT_OBJECT")
|
||||||
|
|
|
@ -214,8 +214,6 @@ class TestCase(test_env.BaseTestCase):
|
||||||
var.setvalue(0, value_to_set)
|
var.setvalue(0, value_to_set)
|
||||||
self.assertEqual(var.getvalue(), value_to_set)
|
self.assertEqual(var.getvalue(), value_to_set)
|
||||||
|
|
||||||
@unittest.skipIf(test_env.get_is_thin(),
|
|
||||||
"thin mode doesn't support database objects yet")
|
|
||||||
def test_4319_var_type_with_object_type(self):
|
def test_4319_var_type_with_object_type(self):
|
||||||
"4319 - test that an object type can be used as type in cursor.var()"
|
"4319 - test that an object type can be used as type in cursor.var()"
|
||||||
obj_type = self.connection.gettype("UDT_OBJECT")
|
obj_type = self.connection.gettype("UDT_OBJECT")
|
||||||
|
|
|
@ -347,7 +347,7 @@ class BaseTestCase(unittest.TestCase):
|
||||||
if obj.type.iscollection:
|
if obj.type.iscollection:
|
||||||
element_values = []
|
element_values = []
|
||||||
for value in obj.aslist():
|
for value in obj.aslist():
|
||||||
if isinstance(value, oracledb.Object):
|
if isinstance(value, oracledb.DbObject):
|
||||||
value = self.get_db_object_as_plain_object(value)
|
value = self.get_db_object_as_plain_object(value)
|
||||||
elif isinstance(value, oracledb.LOB):
|
elif isinstance(value, oracledb.LOB):
|
||||||
value = value.read()
|
value = value.read()
|
||||||
|
@ -356,7 +356,7 @@ class BaseTestCase(unittest.TestCase):
|
||||||
attr_values = []
|
attr_values = []
|
||||||
for attribute in obj.type.attributes:
|
for attribute in obj.type.attributes:
|
||||||
value = getattr(obj, attribute.name)
|
value = getattr(obj, attribute.name)
|
||||||
if isinstance(value, oracledb.Object):
|
if isinstance(value, oracledb.DbObject):
|
||||||
value = self.get_db_object_as_plain_object(value)
|
value = self.get_db_object_as_plain_object(value)
|
||||||
elif isinstance(value, oracledb.LOB):
|
elif isinstance(value, oracledb.LOB):
|
||||||
value = value.read()
|
value = value.read()
|
||||||
|
|
Loading…
Reference in New Issue