Added support for binding and fetching data of type DB_TYPE_OBJECT

(#43).
This commit is contained in:
Anthony Tuininga 2022-11-01 14:33:39 -06:00
parent dd62aeef25
commit 0acdfdb5ad
34 changed files with 2305 additions and 876 deletions

View File

@ -2243,10 +2243,6 @@ when binding data.
Describes columns, attributes or array elements in a database that are an
instance of a named SQL or PL/SQL type.
.. note::
This type is not supported in python-oracledb Thin mode.
.. data:: DB_TYPE_RAW

View File

@ -13,6 +13,10 @@ oracledb 1.2.0b1 (TBD)
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
requiring OS recognition of certificates
(`issue 65 <https://github.com/oracle/python-oracledb/issues/65>`__).

View File

@ -171,7 +171,7 @@ see :ref:`driverdiff` and :ref:`compatibility`.
- Yes
- Yes
* - 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
* - PL/SQL execution (see :ref:`plsqlexecution`)
@ -191,7 +191,7 @@ see :ref:`driverdiff` and :ref:`compatibility`.
- Yes
- Yes
* - SQL and PL/SQL type and collections (see :ref:`fetchobjects`)
- No
- Yes
- Yes
- Yes
* - Query column metadata
@ -518,7 +518,7 @@ values.
- n/a
* - User-defined types (object type, VARRAY, records, collections, SDO_*types)
- DB_TYPE_OBJECT
- Not supported in python-oracledb Thin mode
- Yes
- OBJECT of specific type
Binding of contiguous PL/SQL Index-by BINARY_INTEGER arrays of string, number, and date are

View File

@ -37,8 +37,9 @@
import oracledb
import sample_env
# this script is currently only supported in python-oracledb thick mode
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
# determine whether to use python-oracledb thin mode or thick mode
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(),
password=sample_env.get_main_password(),

View File

@ -34,8 +34,9 @@
import oracledb
import sample_env
# this script is currently only supported in python-oracledb thick mode
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
# determine whether to use python-oracledb thin mode or thick mode
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(),
password=sample_env.get_main_password(),

View File

@ -35,8 +35,9 @@ import datetime
import oracledb
import sample_env
# this script is currently only supported in python-oracledb thick mode
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
# determine whether to use python-oracledb thin mode or thick mode
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(),
password=sample_env.get_main_password(),

View File

@ -49,8 +49,9 @@ import geopandas as gpd
import oracledb
import sample_env
# this script is currently only supported in python-oracledb thick mode
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
# determine whether to use python-oracledb thin mode or thick mode
if not sample_env.get_is_thin():
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
# create Oracle connection and cursor objects
connection = oracledb.connect(user=sample_env.get_main_user(),

View File

@ -41,8 +41,9 @@ import datetime
import oracledb
import sample_env
# this script is currently only supported in python-oracledb thick mode
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
# determine whether to use python-oracledb thin mode or thick mode
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(),
password=sample_env.get_main_password(),

View File

@ -83,12 +83,16 @@ cdef class DbType:
readonly str name
readonly uint32_t default_size
uint32_t _buffer_size_factor
str _ora_name
uint8_t _ora_type_num
uint8_t _csfrm
@staticmethod
cdef DbType _from_num(uint32_t num)
@staticmethod
cdef DbType _from_ora_name(str name)
@staticmethod
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 invoke_session_callback
cdef object _check_value(self, DbType dbtype, BaseDbObjectTypeImpl objtype,
object value, bint* is_ok)
cdef class BasePoolImpl:
cdef:
@ -320,6 +327,7 @@ cdef class BaseVarImpl:
public uint32_t num_elements_in_array
readonly DbType dbtype
readonly BaseDbObjectTypeImpl objtype
BaseConnImpl _conn_impl
int _preferred_num_type
FetchInfo _fetch_info
bint _is_value_set

View File

@ -437,7 +437,7 @@ class Connection:
objects which can be bound to cursors created by this connection.
"""
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)
@property

View File

@ -31,6 +31,7 @@
from typing import Sequence, Union
from . import errors
from . import __name__ as MODULE_NAME
from .base_impl import DbType
@ -42,7 +43,7 @@ class DbObject:
return self._impl.get_attr_value(attr_impl)
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))}>"
def __setattr__(self, name, value):
@ -52,6 +53,15 @@ class DbObject:
attr_impl = self._impl.type.attrs_by_name[name]
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
def _from_impl(cls, impl):
obj = cls.__new__(cls)
@ -73,6 +83,7 @@ class DbObject:
Return a dictionary where the collections indexes are the keys and the
elements are its values.
"""
self._ensure_is_collection()
result = {}
ix = self._impl.get_first_index()
while ix is not None:
@ -84,6 +95,7 @@ class DbObject:
"""
Return a list of each of the collections elements in index order.
"""
self._ensure_is_collection()
result = []
ix = self._impl.get_first_index()
while ix is not None:
@ -106,6 +118,7 @@ class DbObject:
not changed. In other words, the delete operation creates holes in the
collection.
"""
self._ensure_is_collection()
self._impl.delete_by_index(index)
def exists(self, index: int) -> bool:
@ -113,6 +126,7 @@ class DbObject:
Return True or False indicating if an element exists in the collection
at the specified index.
"""
self._ensure_is_collection()
return self._impl.exists_by_index(index)
def extend(self, seq: list) -> None:
@ -121,6 +135,7 @@ class DbObject:
the equivalent of performing append() for each element found in the
sequence.
"""
self._ensure_is_collection()
for value in seq:
self.append(value)
@ -129,6 +144,7 @@ class DbObject:
Return the index of the first element in the collection. If the
collection is empty, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_first_index()
def getelement(self, index: int) -> object:
@ -136,6 +152,7 @@ class DbObject:
Return the element at the specified index of the collection. If no
element exists at that index, an exception is raised.
"""
self._ensure_is_collection()
return self._impl.get_element_by_index(index)
def last(self) -> int:
@ -143,6 +160,7 @@ class DbObject:
Return the index of the last element in the collection. If the
collection is empty, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_last_index()
def next(self, index: int) -> int:
@ -151,6 +169,7 @@ class DbObject:
specified index. If there are no elements in the collection following
the specified index, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_next_index(index)
def prev(self, index: int) -> int:
@ -159,6 +178,7 @@ class DbObject:
specified index. If there are no elements in the collection preceding
the specified index, None is returned.
"""
self._ensure_is_collection()
return self._impl.get_prev_index(index)
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
value.
"""
self._ensure_is_collection()
self._impl.set_element_by_index(index, value)
def size(self) -> int:
"""
Return the number of elements in the collection.
"""
self._ensure_is_collection()
return self._impl.get_size()
def trim(self, num: int) -> None:
"""
Remove the specified number of elements from the end of the collection.
"""
self._ensure_is_collection()
self._impl.trim(num)
@property
@ -237,11 +260,7 @@ class DbObjectType:
return NotImplemented
def __repr__(self):
if self.package_name is not None:
full_name = f"{self.schema}.{self.package_name}.{self.name}"
else:
full_name = f"{self.schema}.{self.name}"
return f"<oracledb.DbObjectType {full_name}>"
return f"<oracledb.DbObjectType {self._get_full_name()}>"
@classmethod
def _from_impl(cls, impl):
@ -251,6 +270,14 @@ class DbObjectType:
typ._element_type = None
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
def attributes(self) -> list:
"""

View File

@ -166,13 +166,15 @@ ERR_INVALID_ACCESS_TOKEN_PARAM = 2031
ERR_INVALID_ACCESS_TOKEN_RETURNED = 2032
ERR_EXPIRED_ACCESS_TOKEN = 2033
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
ERR_TIME_NOT_SUPPORTED = 3000
ERR_FEATURE_NOT_SUPPORTED = 3001
ERR_PYTHON_VALUE_NOT_SUPPORTED = 3002
ERR_PYTHON_TYPE_NOT_SUPPORTED = 3003
ERR_UNSUPPORTED_VAR_SET = 3004
ERR_UNSUPPORTED_TYPE_SET = 3004
ERR_ARRAYS_OF_ARRAYS = 3005
ERR_ORACLE_TYPE_NOT_SUPPORTED = 3006
ERR_DB_TYPE_NOT_SUPPORTED = 3007
@ -180,10 +182,12 @@ ERR_UNSUPPORTED_INBAND_NOTIFICATION = 3008
ERR_SELF_BIND_NOT_SUPPORTED = 3009
ERR_SERVER_VERSION_NOT_SUPPORTED = 3010
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_UNSUPPORTED_VERIFIER_TYPE = 3015
ERR_NO_CRYPTOGRAPHY_PACKAGE = 3016
ERR_ORACLE_TYPE_NAME_NOT_SUPPORTED = 3017
ERR_TDS_TYPE_NOT_SUPPORTED = 3018
# error numbers that result in DatabaseError
ERR_TNS_ENTRY_NOT_FOUND = 4000
@ -221,6 +225,7 @@ ERR_INTEGER_TOO_LARGE = 5002
ERR_UNEXPECTED_NEGATIVE_INTEGER = 5003
ERR_UNEXPECTED_DATA = 5004
ERR_UNEXPECTED_REFUSE = 5005
ERR_UNEXPECTED_END_OF_DATA = 5006
# error numbers that result in OperationalError
ERR_LISTENER_REFUSED_CONNECTION = 6000
@ -235,6 +240,7 @@ ERR_ORACLE_ERROR_XREF = {
28: ERR_CONNECTION_CLOSED,
600: ERR_CONNECTION_CLOSED,
1005: ERR_NO_CREDENTIALS,
22303: (ERR_INVALID_OBJECT_TYPE_NAME, 'type "(?P<name>[^"]*"."[^"]*)"'),
24422: ERR_POOL_HAS_BUSY_CONNECTIONS,
24349: ERR_ARRAY_DML_ROW_COUNTS_NOT_ENABLED,
24459: ERR_POOL_NO_CONNECTION_AVAILABLE,
@ -274,7 +280,7 @@ ERR_MESSAGE_FORMATS = {
ERR_ARRAYS_OF_ARRAYS:
'arrays of arrays are not supported',
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',
ERR_CALL_TIMEOUT_EXCEEDED:
'call timeout of {timeout} ms exceeded',
@ -338,6 +344,8 @@ ERR_MESSAGE_FORMATS = {
'"{name}" argument contains invalid values',
ERR_INVALID_NUMBER:
'invalid number',
ERR_INVALID_OBJECT_TYPE_NAME:
'invalid object type name: "{name}"',
ERR_INVALID_OCI_ATTR_TYPE:
'invalid OCI attribute type {attr_type}',
ERR_INVALID_POOL_CLASS:
@ -414,8 +422,12 @@ ERR_MESSAGE_FORMATS = {
'invalid number: empty exponent',
ERR_NUMBER_WITH_INVALID_EXPONENT:
'invalid number: invalid exponent',
ERR_OBJECT_IS_NOT_A_COLLECTION:
'object {name} is not a collection',
ERR_ORACLE_NUMBER_NO_REPR:
'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:
'Oracle data type {num} is not supported',
ERR_POOL_HAS_BUSY_CONNECTIONS:
@ -437,6 +449,8 @@ ERR_MESSAGE_FORMATS = {
ERR_SERVER_VERSION_NOT_SUPPORTED:
'connections to this database server version are not supported '
'by python-oracledb in thin mode',
ERR_TDS_TYPE_NOT_SUPPORTED:
'Oracle TDS data type {num} is not supported',
ERR_THIN_CONNECTION_ALREADY_CREATED:
'python-oracledb thick mode cannot be used because a thin mode '
'connection has already been created',
@ -450,6 +464,9 @@ ERR_MESSAGE_FORMATS = {
'{config_dir}',
ERR_UNEXPECTED_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:
'internal error: read a negative integer when expecting a '
'positive integer',
@ -458,11 +475,11 @@ ERR_MESSAGE_FORMATS = {
'format was returned',
ERR_UNSUPPORTED_INBAND_NOTIFICATION:
'unsupported in-band notification with error number {err_num}',
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_VAR:
'unsupported Python type {py_type_name} for variable '
ERR_UNSUPPORTED_PYTHON_TYPE_FOR_DB_TYPE:
'unsupported Python type {py_type_name} for database type '
'{db_type_name}',
ERR_UNSUPPORTED_VAR_SET:
'variable of type {db_type_name} does not support being set',
ERR_UNSUPPORTED_TYPE_SET:
'type {db_type_name} does not support being set',
ERR_UNSUPPORTED_VERIFIER_TYPE:
'password verifier type 0x{verifier_type:x} is not supported by '
'python-oracledb in thin mode',

View File

@ -35,6 +35,119 @@ cdef class BaseConnImpl:
self.dsn = dsn
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")
def _get_oci_attr(self, uint32_t handle_type, uint32_t attr_num,
uint32_t attr_type):
@ -123,7 +236,7 @@ cdef class BaseConnImpl:
pass
@utils.CheckImpls("getting an object type")
def get_type(self, str name):
def get_type(self, object conn, str name):
pass
@utils.CheckImpls("getting the database version")

View File

@ -31,8 +31,18 @@
cdef class BaseDbObjectImpl:
@utils.CheckImpls("appending a value to a collection")
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
@utils.CheckImpls("creating a copy of an object")
@ -75,12 +85,31 @@ cdef class BaseDbObjectImpl:
def get_size(self):
pass
@utils.CheckImpls("setting an attribute 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
@utils.CheckImpls("setting an element of a collection")
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
@utils.CheckImpls("trimming elements from a collection")

View File

@ -51,20 +51,23 @@ cdef class ApiType:
cdef dict db_type_by_num = {}
cdef dict db_type_by_ora_name = {}
cdef dict db_type_by_ora_type_num = {}
cdef class DbType:
def __init__(self, num, name, ora_type_num, default_size=0, csfrm=0,
buffer_size_factor=0):
def __init__(self, num, name, ora_name, ora_type_num, default_size=0,
csfrm=0, buffer_size_factor=0):
cdef uint16_t ora_type_key = csfrm * 256 + ora_type_num
self.num = num
self.name = name
self.default_size = default_size
self._ora_name = ora_name
self._ora_type_num = ora_type_num
self._csfrm = csfrm
self._buffer_size_factor = buffer_size_factor
db_type_by_num[num] = self
db_type_by_ora_name[ora_name] = self
db_type_by_ora_type_num[ora_type_key] = self
def __reduce__(self):
@ -81,6 +84,14 @@ cdef class DbType:
pass
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
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
@ -93,62 +104,75 @@ cdef class DbType:
# 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", 101,
"DB_TYPE_BINARY_DOUBLE", "BINARY_DOUBLE", 101,
buffer_size_factor=8)
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", 3,
"DB_TYPE_BINARY_INTEGER", "BINARY_INTEGER", 3,
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)
DB_TYPE_BOOLEAN = DbType(DB_TYPE_NUM_BOOLEAN, "DB_TYPE_BOOLEAN", 252,
buffer_size_factor=4)
DB_TYPE_CHAR = DbType(DB_TYPE_NUM_CHAR, "DB_TYPE_CHAR", 96, 2000, csfrm=1,
buffer_size_factor=4)
DB_TYPE_CLOB = DbType(DB_TYPE_NUM_CLOB, "DB_TYPE_CLOB", 112, csfrm=1,
DB_TYPE_BOOLEAN = DbType(DB_TYPE_NUM_BOOLEAN, "DB_TYPE_BOOLEAN", "BOOLEAN",
252, buffer_size_factor=4)
DB_TYPE_CHAR = DbType(DB_TYPE_NUM_CHAR, "DB_TYPE_CHAR", "CHAR", 96, 2000,
csfrm=1, buffer_size_factor=4)
DB_TYPE_CLOB = DbType(DB_TYPE_NUM_CLOB, "DB_TYPE_CLOB", "CLOB", 112, csfrm=1,
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)
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)
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",
182)
DB_TYPE_JSON = DbType(DB_TYPE_NUM_JSON, "DB_TYPE_JSON", 119)
DB_TYPE_LONG = DbType(DB_TYPE_NUM_LONG_VARCHAR, "DB_TYPE_LONG", 8, csfrm=1,
buffer_size_factor=2147483647)
"INTERVAL YEAR TO MONTH", 182)
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", "LONG",
8, csfrm=1, buffer_size_factor=2147483647)
DB_TYPE_LONG_NVARCHAR = DbType(DB_TYPE_NUM_LONG_NVARCHAR,
"DB_TYPE_LONG_NVARCHAR", 8, csfrm=2,
buffer_size_factor=2147483647)
DB_TYPE_LONG_RAW = DbType(DB_TYPE_NUM_LONG_RAW, "DB_TYPE_LONG_RAW", 24,
buffer_size_factor=2147483647)
DB_TYPE_NCHAR = DbType(DB_TYPE_NUM_NCHAR, "DB_TYPE_NCHAR", 96, 2000, csfrm=2,
buffer_size_factor=4)
DB_TYPE_NCLOB = DbType(DB_TYPE_NUM_NCLOB, "DB_TYPE_NCLOB", 112, csfrm=2,
buffer_size_factor=112)
DB_TYPE_NUMBER = DbType(DB_TYPE_NUM_NUMBER, "DB_TYPE_NUMBER", 2,
"DB_TYPE_LONG_NVARCHAR", "LONG NVARCHAR", 8,
csfrm=2, buffer_size_factor=2147483647)
DB_TYPE_LONG_RAW = DbType(DB_TYPE_NUM_LONG_RAW, "DB_TYPE_LONG_RAW", "LONG RAW",
24, buffer_size_factor=2147483647)
DB_TYPE_NCHAR = DbType(DB_TYPE_NUM_NCHAR, "DB_TYPE_NCHAR", "NCHAR", 96, 2000,
csfrm=2, buffer_size_factor=4)
DB_TYPE_NCLOB = DbType(DB_TYPE_NUM_NCLOB, "DB_TYPE_NCLOB", "NCLOB", 112,
csfrm=2, buffer_size_factor=112)
DB_TYPE_NUMBER = DbType(DB_TYPE_NUM_NUMBER, "DB_TYPE_NUMBER", "NUMBER", 2,
buffer_size_factor=22)
DB_TYPE_NVARCHAR = DbType(DB_TYPE_NUM_NVARCHAR, "DB_TYPE_NVARCHAR", 1, 4000,
csfrm=2, buffer_size_factor=4)
DB_TYPE_OBJECT = DbType(DB_TYPE_NUM_OBJECT, "DB_TYPE_OBJECT", 109)
DB_TYPE_RAW = DbType(DB_TYPE_NUM_RAW, "DB_TYPE_RAW", 23, 4000,
DB_TYPE_NVARCHAR = DbType(DB_TYPE_NUM_NVARCHAR, "DB_TYPE_NVARCHAR",
"NVARCHAR2", 1, 4000, csfrm=2, buffer_size_factor=4)
DB_TYPE_OBJECT = DbType(DB_TYPE_NUM_OBJECT, "DB_TYPE_OBJECT", "OBJECT", 109)
DB_TYPE_RAW = DbType(DB_TYPE_NUM_RAW, "DB_TYPE_RAW", "RAW", 23, 4000,
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)
DB_TYPE_TIMESTAMP = DbType(DB_TYPE_NUM_TIMESTAMP, "DB_TYPE_TIMESTAMP", 180,
buffer_size_factor=11)
DB_TYPE_TIMESTAMP = DbType(DB_TYPE_NUM_TIMESTAMP, "DB_TYPE_TIMESTAMP",
"TIMESTAMP", 180, buffer_size_factor=11)
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)
DB_TYPE_TIMESTAMP_TZ = DbType(DB_TYPE_NUM_TIMESTAMP_TZ, "DB_TYPE_TIMESTAMP_TZ",
181, buffer_size_factor=13)
DB_TYPE_UROWID = DbType(DB_TYPE_NUM_UROWID, "DB_TYPE_UROWID", 208)
DB_TYPE_VARCHAR = DbType(DB_TYPE_NUM_VARCHAR, "DB_TYPE_VARCHAR", 1, 4000,
csfrm=1, buffer_size_factor=4)
"TIMESTAMP WITH TZ", 181,
buffer_size_factor=13)
DB_TYPE_UROWID = DbType(DB_TYPE_NUM_UROWID, "DB_TYPE_UROWID", "UROWID", 208)
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
BINARY = ApiType("BINARY", DB_TYPE_RAW, DB_TYPE_LONG_RAW)

View File

@ -49,115 +49,25 @@ cdef class BaseVarImpl:
exception is raised when the Python value is found to be unacceptable;
otherwise, the flag is cleared if the Python value is unacceptable.
"""
cdef:
uint32_t db_type_num, size
bint type_ok = True
BaseLobImpl lob_impl
object orig_value
cdef uint32_t size
# call in converter, if applicable
if self.inconverter is not None:
value = self.inconverter(value)
# if value is None, no further checks are needed
if value is None:
self._set_scalar_value(pos, value)
self._is_value_set = True
# check the value and verify it is acceptable
value = self._conn_impl._check_value(self.dbtype, self.objtype, value,
was_set)
if was_set != NULL and not was_set[0]:
return 0
# check to see that the type of value is accepted; perform any
# necessary adjustments
db_type_num = self.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):
type_ok = isinstance(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)
# resize variable, if applicable
if value is not None and self.dbtype.default_size != 0:
size = <uint32_t> len(value)
if size > self.size:
self._resize(size)
# set value
self._set_scalar_value(pos, value)
self._is_value_set = True

View File

@ -507,7 +507,7 @@ cdef class ThickConnImpl(BaseConnImpl):
_raise_from_odpi()
return value
def get_type(self, str name):
def get_type(self, object conn, str name):
cdef:
dpiObjectType *handle
const char *name_ptr

View File

@ -50,7 +50,7 @@ cdef class ThickDbObjectImpl(BaseDbObjectImpl):
data.isNull = 0
_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.
"""
@ -204,7 +204,7 @@ cdef class ThickDbObjectImpl(BaseDbObjectImpl):
_raise_from_odpi()
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.
"""
@ -222,7 +222,7 @@ cdef class ThickDbObjectImpl(BaseDbObjectImpl):
native_type_num, &data) < 0:
_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
integers.

View File

@ -37,7 +37,6 @@ cdef class ThickVarImpl(BaseVarImpl):
uint32_t _native_type_num
bint _get_returned_data
object _conn
ThickConnImpl _conn_impl
def __dealloc__(self):
if self._handle != NULL:
@ -75,6 +74,7 @@ cdef class ThickVarImpl(BaseVarImpl):
cdef int _create_handle(self) except -1:
cdef:
ThickConnImpl conn_impl = self._conn_impl
dpiObjectType *obj_type_handle = NULL
ThickDbObjectTypeImpl obj_type_impl
if self._handle != NULL:
@ -84,7 +84,7 @@ cdef class ThickVarImpl(BaseVarImpl):
obj_type_impl = <ThickDbObjectTypeImpl> self.objtype
obj_type_handle = obj_type_impl._handle
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,
0, self.is_array, obj_type_handle, &self._handle,
&self._data) < 0:

File diff suppressed because it is too large Load Diff

View File

@ -65,6 +65,7 @@ cdef class ThinConnImpl(BaseConnImpl):
uint32_t _temp_lobs_total_size
uint32_t _call_timeout
str _cclass
int _dbobject_type_cache_num
def __init__(self, str dsn, ConnectParamsImpl params):
if not HAS_CRYPTOGRAPHY:
@ -284,6 +285,9 @@ cdef class ThinConnImpl(BaseConnImpl):
def close(self, bint in_del=False):
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)
except (ssl.SSLError, exceptions.DatabaseError):
pass
@ -299,6 +303,7 @@ cdef class ThinConnImpl(BaseConnImpl):
self._statement_cache = collections.OrderedDict()
self._statement_cache_size = params.stmtcachesize
self._statement_cache_lock = threading.Lock()
self._dbobject_type_cache_num = create_new_dbobject_type_cache(self)
self._cursors_to_close = array.array('I')
array.resize(self._cursors_to_close, TNS_MAX_CURSORS_TO_CLOSE)
self.invoke_session_callback = True
@ -334,6 +339,11 @@ cdef class ThinConnImpl(BaseConnImpl):
def get_stmt_cache_size(self):
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):
return self._server_version

View File

@ -413,6 +413,44 @@ DEF TNS_BIND_DIR_OUTPUT = 16
DEF TNS_BIND_DIR_INPUT = 32
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
DEF TNS_EXEC_OPTION_PARSE = 0x01
DEF TNS_EXEC_OPTION_BIND = 0x08
@ -627,6 +665,7 @@ DEF TNS_BASE64_ALPHABET = \
b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
cdef bytearray TNS_BASE64_ALPHABET_ARRAY = \
bytearray(TNS_BASE64_ALPHABET)
cdef bytes TNS_EXTENT_OID = bytes.fromhex('00000000000000000000000000010001')
# purity types
DEF PURITY_DEFAULT = 0

View File

@ -50,7 +50,10 @@ cdef class ThinCursorImpl(BaseCursorImpl):
self._statement = None
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):
"""
@ -114,14 +117,18 @@ cdef class ThinCursorImpl(BaseCursorImpl):
name=bind_info._bind_name)
def execute(self, cursor):
cdef MessageWithData message
self._preprocess_execute(cursor.connection)
cdef:
object conn = cursor.connection
MessageWithData message
self._preprocess_execute(conn)
message = self._create_message(ExecuteMessage, cursor)
message.num_execs = 1
self._conn_impl._protocol._process_single_message(message)
self._statement._requires_full_execute = False
if self._statement._is_query:
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):
cdef:

View File

@ -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]

View File

@ -290,6 +290,7 @@ cdef class Message:
cdef class MessageWithData(Message):
cdef:
ThinDbObjectTypeCache type_cache
ThinCursorImpl cursor_impl
array.array bit_vector_buf
const char_type *bit_vector
@ -521,6 +522,7 @@ cdef class MessageWithData(Message):
uint8_t num_bytes, ora_type_num, csfrm
ThinCursorImpl cursor_impl
object column_value = None
ThinDbObjectImpl obj_impl
int32_t actual_num_bytes
uint32_t buffer_size
FetchInfo fetch_info
@ -536,9 +538,10 @@ cdef class MessageWithData(Message):
buffer_size = var_impl.buffer_size
if var_impl.bypass_decode:
ora_type_num = TNS_DATA_TYPE_RAW
if buffer_size == 0 and ora_type_num != TNS_DATA_TYPE_LONG \
and ora_type_num != TNS_DATA_TYPE_LONG_RAW \
and ora_type_num != TNS_DATA_TYPE_UROWID:
if buffer_size == 0 and self.in_fetch \
and ora_type_num not in (TNS_DATA_TYPE_LONG,
TNS_DATA_TYPE_LONG_RAW,
TNS_DATA_TYPE_UROWID):
column_value = None # column is null by describe
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
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:
column_value = buf.read_interval_ds()
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:
errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED,
name=var_impl.dbtype.name)
@ -615,11 +628,15 @@ cdef class MessageWithData(Message):
cdef FetchInfo _process_column_info(self, ReadBuffer buf,
ThinCursorImpl cursor_impl):
cdef:
ThinDbObjectTypeImpl typ_impl
uint8_t data_type, csfrm
int8_t precision, scale
uint8_t nulls_allowed
FetchInfo fetch_info
uint32_t num_bytes
str schema, name
int cache_num
bytes oid
buf.read_ub1(&data_type)
fetch_info = FetchInfo()
buf.skip_ub1() # flags
@ -639,7 +656,7 @@ cdef class MessageWithData(Message):
buf.skip_ub4() # cont flags
buf.read_ub4(&num_bytes) # OID
if num_bytes > 0:
buf.skip_raw_bytes(num_bytes + 1)
oid = buf.read_bytes()
buf.skip_ub2() # version
buf.skip_ub2() # character set id
buf.read_ub1(&csfrm) # character set form
@ -657,14 +674,19 @@ cdef class MessageWithData(Message):
fetch_info._name = buf.read_str(TNS_CS_IMPLICIT)
buf.read_ub4(&num_bytes)
if num_bytes > 0:
buf.skip_ub1() # skip repeated length
buf.skip_raw_bytes(num_bytes) # schema name
schema = buf.read_str(TNS_CS_IMPLICIT)
buf.read_ub4(&num_bytes)
if num_bytes > 0:
buf.skip_ub1() # skip repeated length
buf.skip_raw_bytes(num_bytes) # type name
name = buf.read_str(TNS_CS_IMPLICIT)
buf.skip_ub2() # column position
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
cdef int _process_describe_info(self, ReadBuffer buf,
@ -894,6 +916,7 @@ cdef class MessageWithData(Message):
cdef int _write_column_metadata(self, WriteBuffer buf,
list bind_var_impls) except -1:
cdef:
ThinDbObjectTypeImpl typ_impl
uint8_t ora_type_num, flag
uint32_t buffer_size
ThinVarImpl var_impl
@ -921,8 +944,14 @@ cdef class MessageWithData(Message):
else:
buf.write_ub4(0) # max num elements
buf.write_ub4(0) # cont flag
buf.write_ub4(0) # OID
buf.write_ub4(0) # version
if var_impl.objtype is not None:
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:
buf.write_ub4(TNS_CHARSET_UTF8)
else:
@ -937,6 +966,7 @@ cdef class MessageWithData(Message):
object value) except -1:
cdef:
uint8_t ora_type_num = var_impl.dbtype._ora_type_num
ThinDbObjectTypeImpl typ_impl
ThinCursorImpl cursor_impl
ThinLobImpl lob_impl
uint32_t num_bytes
@ -945,6 +975,13 @@ cdef class MessageWithData(Message):
if ora_type_num == TNS_DATA_TYPE_BOOLEAN:
buf.write_uint8(TNS_ESCAPE_CHAR)
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:
buf.write_uint8(0)
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
@ -955,10 +992,10 @@ cdef class MessageWithData(Message):
else:
buf._caps._check_ncharset_id()
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 \
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 \
or ora_type_num == TNS_DATA_TYPE_BINARY_INTEGER:
if isinstance(value, bool):
@ -986,22 +1023,17 @@ cdef class MessageWithData(Message):
buf.write_ub4(1)
buf.write_ub4(cursor_impl._statement._cursor_id)
elif ora_type_num == TNS_DATA_TYPE_BOOLEAN:
if value:
buf.write_uint8(2)
buf.write_uint16(0x0101)
else:
buf.write_uint16(0x0100)
buf.write_bool(value)
elif ora_type_num == TNS_DATA_TYPE_INTERVAL_DS:
buf.write_interval_ds(value)
elif ora_type_num == TNS_DATA_TYPE_CLOB \
or ora_type_num == TNS_DATA_TYPE_BLOB:
lob_impl = value._impl
num_bytes = <uint32_t> len(lob_impl._locator)
buf.write_ub4(num_bytes)
buf.write_bytes_chunked(lob_impl._locator)
buf.write_lob_with_length(value._impl)
elif ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
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:
errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED,
name=var_impl.dbtype.name)
@ -1461,10 +1493,10 @@ cdef class AuthMessage(Message):
uint32_t key_len = <uint32_t> len(key_bytes)
uint32_t value_len = <uint32_t> len(value_bytes)
buf.write_ub4(key_len)
buf.write_bytes_chunked(key_bytes)
buf.write_bytes_with_length(key_bytes)
buf.write_ub4(value_len)
if value_len > 0:
buf.write_bytes_chunked(value_bytes)
buf.write_bytes_with_length(value_bytes)
buf.write_ub4(flags)
cdef int _write_message(self, WriteBuffer buf) except -1:
@ -2049,7 +2081,7 @@ cdef class LobOpMessage(Message):
buf.write_ub4(TNS_CHARSET_UTF8)
if self.data is not None:
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:
buf.write_ub8(self.amount) # LOB amount

View File

@ -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)

View File

@ -145,5 +145,6 @@ def init_thin_impl(package):
Initializes globals after the package has been completely initialized. This
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

View File

@ -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 BaseLobImpl, BasePoolImpl, FetchInfo
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_BINARY_INTEGER, DB_TYPE_CURSOR, DB_TYPE_OBJECT
ctypedef unsigned char char_type
cdef type PY_TYPE_DECIMAL = decimal.Decimal
cdef type PY_TYPE_DB_OBJECT
cdef type PY_TYPE_LOB
cdef bint HAS_CRYPTOGRAPHY = True
@ -93,6 +96,7 @@ include "impl/thin/utils.pyx"
include "impl/thin/crypto.pyx"
include "impl/thin/capabilities.pyx"
include "impl/thin/buffer.pyx"
include "impl/thin/packet.pyx"
include "impl/thin/network_services.pyx"
include "impl/thin/data_types.pyx"
include "impl/thin/messages.pyx"
@ -101,6 +105,7 @@ include "impl/thin/connection.pyx"
include "impl/thin/statement.pyx"
include "impl/thin/cursor.pyx"
include "impl/thin/var.pyx"
include "impl/thin/dbobject.pyx"
include "impl/thin/lob.pyx"
include "impl/thin/pool.pyx"
include "impl/thin/conversions.pyx"

View File

@ -202,8 +202,6 @@ class TestCase(test_env.BaseTestCase):
]
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):
"1607 - test inserting an object with DML returning"
type_obj = self.connection.gettype("UDT_OBJECT")

View File

@ -33,8 +33,6 @@ import unittest
import oracledb
import test_env
@unittest.skipIf(test_env.get_is_thin(),
"thin mode doesn't support database objects yet")
class TestCase(test_env.BaseTestCase):
def __test_data(self, expected_int_value, expected_obj_value,
@ -309,7 +307,7 @@ class TestCase(test_env.BaseTestCase):
sub_obj.SUBSTRINGVALUE = "Substring value"
obj.SUBOBJECTVALUE = sub_obj
self.cursor.execute("insert into TestObjects (IntCol, ObjectCol) " \
"values (4, :obj)", obj = obj)
"values (4, :obj)", obj=obj)
self.cursor.execute("""
select IntCol, ObjectCol, ArrayCol
from TestObjects
@ -377,7 +375,7 @@ class TestCase(test_env.BaseTestCase):
def test_2308_invalid_type_object(self):
"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,
"A TYPE THAT DOES NOT EXIST")
@ -387,7 +385,7 @@ class TestCase(test_env.BaseTestCase):
collection_obj = collection_obj_type.newobject()
array_obj_type = self.connection.gettype("UDT_ARRAY")
array_obj = array_obj_type.newobject()
self.assertRaisesRegex(oracledb.DatabaseError, "^DPI-1056:",
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-2008:",
collection_obj.append, array_obj)
def test_2310_referencing_sub_obj(self):
@ -425,7 +423,7 @@ class TestCase(test_env.BaseTestCase):
obj = obj_type.newobject()
wrong_obj_type = self.connection.gettype("UDT_OBJECTARRAY")
wrong_obj = wrong_obj_type.newobject()
self.assertRaisesRegex(oracledb.DatabaseError, "^DPI-1056:", setattr,
self.assertRaisesRegex(oracledb.DatabaseError, "^DPY-2008:", setattr,
obj, "SUBOBJECTVALUE", wrong_obj)
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"
sub_obj_type = self.connection.gettype("UDT_SUBOBJECT")
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()
for num_val, str_val in data:
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)
array_obj.trim(2)
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
data[:2])
data[:3])
array_obj.trim(1)
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
data[:1])
data[:2])
array_obj.trim(0)
self.assertEqual(self.get_db_object_as_plain_object(array_obj),
data[:1])
array_obj.trim(1)
data[:2])
array_obj.trim(2)
self.assertEqual(self.get_db_object_as_plain_object(array_obj), [])
def test_2316_sql_type_metadata(self):

View File

@ -68,8 +68,6 @@ class TestCase(test_env.BaseTestCase):
count, = self.cursor.fetchone()
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):
"3202 - test binding a boolean collection (in)"
type_obj = self.connection.gettype("PKG_TESTBOOLEANS.UDT_BOOLEANLIST")
@ -80,8 +78,6 @@ class TestCase(test_env.BaseTestCase):
(obj,))
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):
"3203 - test binding a boolean collection (out)"
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.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):
"3204 - test binding a PL/SQL date collection (in)"
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))
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):
"3205 - test binding a PL/SQL date collection (in/out)"
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
@ -122,8 +114,6 @@ class TestCase(test_env.BaseTestCase):
]
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):
"3206 - test binding a PL/SQL date collection (out)"
type_obj = self.connection.gettype("PKG_TESTDATEARRAYS.UDT_DATELIST")
@ -136,8 +126,6 @@ class TestCase(test_env.BaseTestCase):
]
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):
"3207 - test binding a PL/SQL number collection (in)"
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
@ -149,8 +137,6 @@ class TestCase(test_env.BaseTestCase):
(5, obj))
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):
"3208 - test binding a PL/SQL number collection (in/out)"
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
@ -161,8 +147,6 @@ class TestCase(test_env.BaseTestCase):
self.cursor.callproc("pkg_TestNumberArrays.TestInOutArrays", (4, obj))
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):
"3209 - test binding a PL/SQL number collection (out)"
type_name = "PKG_TESTNUMBERARRAYS.UDT_NUMBERLIST"
@ -171,8 +155,6 @@ class TestCase(test_env.BaseTestCase):
self.cursor.callproc("pkg_TestNumberArrays.TestOutArrays", (3, obj))
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):
"3210 - test binding an array of PL/SQL records (in)"
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', " \
"'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):
"3211 - test binding a PL/SQL record (in)"
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', " \
"'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):
"3212 - test binding a PL/SQL record (out)"
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.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):
"3213 - test binding a PL/SQL string collection (in)"
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
@ -262,8 +238,6 @@ class TestCase(test_env.BaseTestCase):
(5, obj))
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):
"3214 - test binding a PL/SQL string collection (in/out)"
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
@ -280,8 +254,6 @@ class TestCase(test_env.BaseTestCase):
]
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):
"3215 - test binding a PL/SQL string collection (out)"
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"
@ -296,8 +268,6 @@ class TestCase(test_env.BaseTestCase):
]
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):
"3216 - test binding a PL/SQL string collection (out with holes)"
type_name = "PKG_TESTSTRINGARRAYS.UDT_STRINGLIST"

View File

@ -42,7 +42,7 @@ class TestCase(test_env.BaseTestCase):
result = var.getvalue()
if isinstance(result, oracledb.LOB):
result = result.read()
elif isinstance(result, oracledb.Object):
elif isinstance(result, oracledb.DbObject):
result = self.get_db_object_as_plain_object(result)
if isinstance(expected_value, datetime.date) \
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, "523lkhlf")
@unittest.skipIf(test_env.get_is_thin(),
"thin mode doesn't support database objects yet")
def test_3721_DB_TYPE_OBJECT(self):
"3721 - setting values on variables of type DB_TYPE_OBJECT"
obj_type = self.connection.gettype("UDT_OBJECT")

View File

@ -214,8 +214,6 @@ class TestCase(test_env.BaseTestCase):
var.setvalue(0, 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):
"4319 - test that an object type can be used as type in cursor.var()"
obj_type = self.connection.gettype("UDT_OBJECT")

View File

@ -347,7 +347,7 @@ class BaseTestCase(unittest.TestCase):
if obj.type.iscollection:
element_values = []
for value in obj.aslist():
if isinstance(value, oracledb.Object):
if isinstance(value, oracledb.DbObject):
value = self.get_db_object_as_plain_object(value)
elif isinstance(value, oracledb.LOB):
value = value.read()
@ -356,7 +356,7 @@ class BaseTestCase(unittest.TestCase):
attr_values = []
for attribute in obj.type.attributes:
value = getattr(obj, attribute.name)
if isinstance(value, oracledb.Object):
if isinstance(value, oracledb.DbObject):
value = self.get_db_object_as_plain_object(value)
elif isinstance(value, oracledb.LOB):
value = value.read()