Skip to content

Commit 02a06bf

Browse files
All connect strings are parsed in the driver.
1 parent 28df389 commit 02a06bf

File tree

13 files changed

+187
-87
lines changed

13 files changed

+187
-87
lines changed

doc/src/release_notes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ Common Changes
6868
#) Added :meth:`oracledb.register_password_type()` to allow users to register
6969
a function that will be called when a password is supplied as a dictionary
7070
containing the key "type".
71+
#) All connect strings are now parsed by the driver. Previously, only thin
72+
mode parsed all connect strings and thick mode passed the connect string
73+
unchanged to the Oracle Client library to parse. Parameters unrecognized by
74+
the driver in Easy Connect strings are now ignored. Parameters unrecognized
75+
by the driver in the ``CONNECT_DATA`` section of a full connect descriptor
76+
are passed through unchanged. All other parameters in other sections of a
77+
full connect deescriptor that are unrecognized by the driver are ignored.
7178
#) Added attributes :attr:`DbObjectAttribute.precision`,
7279
:attr:`DbObjectAttribute.scale`, and :attr:`DbObjectAttribute.max_size` that
7380
provide additional metadata about

doc/src/user_guide/connection_handling.rst

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,16 @@ If you like to encapsulate values, parameters can be passed using a
129129
conn = oracledb.connect(user="my_user", password="my_password", params=params)
130130
131131
Some values such as the database host name can be specified as ``connect()``
132-
parameters, as part of the connect string, and in the ``params`` object. If a
133-
``dsn`` is passed, the python-oracledb :ref:`Thick <enablingthick>` mode will
134-
use the ``dsn`` string to connect. Otherwise, a connection string is internally
135-
constructed from the individual parameters and ``params`` object values, with
136-
the individual parameters having precedence. In python-oracledb's default Thin
137-
mode, a connection string is internally used that contains all relevant values
138-
specified. The precedence in Thin mode is that values in any ``dsn`` parameter
139-
override values passed as individual parameters, which themselves override
140-
values set in the ``params`` object. Similar precedence rules also apply to
141-
other values.
132+
parameters, as part of the connect string, and in the ``params`` object. If a
133+
``dsn`` is passed, a connection string is internally constructed from the
134+
individual parameters and ``params`` object values, with the individual
135+
parameters having precedence. The precedence is that values in any ``dsn``
136+
parameter override values passed as individual parameters, which themselves
137+
override values set in the ``params`` object. Similar precedence rules also
138+
apply to other values.
142139

143140
A single, combined connection string can be passed to ``connect()`` but this
144-
may cause complications if the password contains '@' or '/' characters:
141+
may cause complications if the password contains "@" or "/" characters:
145142

146143
.. code-block:: python
147144
@@ -329,26 +326,37 @@ If the database is using a non-default port, it must be specified:
329326
The Easy Connect syntax supports Oracle Database service names. It cannot be
330327
used with the older System Identifiers (SID).
331328

332-
The latest `Easy Connect <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&
329+
The `Easy Connect <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&
333330
id=GUID-8C85D289-6AF3-41BC-848B-BF39D32648BA>`__ syntax allows the use of
334331
multiple hosts or ports, along with optional entries for the wallet location,
335332
the distinguished name of the database server, and allows some network
336333
configuration options such as the connection timeout and keep-alive values to
337-
be set. This means that a :ref:`sqlnet.ora <optnetfiles>` file is not needed
338-
for some common connection scenarios. See the technical brief `Oracle Database
339-
Easy Connect Plus <https://download.oracle.com/ocomdocs/global/Oracle-Net-Easy
340-
-Connect-Plus.pdf>`__ for more information.
334+
be set::
341335

342-
In python-oracledb Thin mode, any unknown Easy Connect options are ignored and
343-
are not passed to the database. See :ref:`Connection String Differences
344-
<diffconnstr>` for more information.
336+
.. code-block:: python
345337
346-
In python-oracledb Thick mode, it is the Oracle Client libraries that parse the
347-
Easy Connect string. Check the Easy Connect Naming method in `Oracle Net
348-
Service Administrator's Guide
349-
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&
350-
id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE>`__ for the syntax to use in your
351-
version of the Oracle Client libraries.
338+
connection = oracledb.connect(user="hr", password=userpwd,
339+
dsn="dbhost.example.com/orclpdb?expire_time=2")
340+
341+
This means that a :ref:`sqlnet.ora <optnetfiles>` file is not needed for common
342+
connection scenarios. See the technical brief `Oracle Database Easy Connect
343+
Plus <https://download.oracle.com/ocomdocs/global/Oracle-Net-Easy
344+
-Connect-Plus.pdf>`__ for additional information.
345+
346+
Python-oracledb specific settings can also be passed as Easy Connect arguments.
347+
For example to set the statement cache size used by connections::
348+
349+
.. code-block:: python
350+
351+
connection = oracledb.connect(user="hr", password=userpwd,
352+
dsn="dbhost.example.com/orclpdb?pyo.stmtcachesize=50")
353+
354+
See :ref:`defineconnparams` and :ref:`definepoolparams` for the settings that
355+
can be passed as arguments.
356+
357+
Any Easy Connect parameters that are unknown to python-oracledb are ignored and
358+
not passed to the database. See :ref:`Connection String Differences
359+
<diffconnstr>` for more information.
352360

353361
.. _conndescriptor:
354362

@@ -386,6 +394,9 @@ This prints::
386394

387395
(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=dbhost.example.com)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orclpdb))(SECURITY=(SSL_SERVER_DN_MATCH=True)))
388396

397+
The ``CONNECT_DATA`` parameters of a full connect descriptor that are
398+
unrecognized by python-oracledb are passed to the database unchanged.
399+
389400
.. _netservice:
390401

391402
TNS Aliases for Connection Strings
@@ -1216,12 +1227,9 @@ Note :meth:`ConnectParams.set()` has no effect after
12161227

12171228
Some values such as the database host name can be specified as
12181229
:func:`oracledb.connect()`, parameters, as part of the connect string, and in
1219-
the ``params`` object. If a ``dsn`` is passed, the python-oracledb :ref:`Thick
1220-
<enablingthick>` mode will use the ``dsn`` string to connect. Otherwise, a
1221-
connection string is internally constructed from the individual parameters and
1222-
``params`` object values, with the individual parameters having precedence. In
1223-
python-oracledb's default Thin mode, a connection string is internally used
1224-
that contains all relevant values specified. The precedence in Thin mode is
1230+
the ``params`` object. If a ``dsn`` is passed, a connection string is
1231+
internally constructed from the individual parameters and ``params`` object
1232+
values, with the individual parameters having precedence. The precedence is
12251233
that values in any ``dsn`` parameter override values passed as individual
12261234
parameters, which themselves override values set in the ``params`` object.
12271235
Similar precedence rules also apply to other values.
@@ -2454,15 +2462,12 @@ individually using the ``set()`` method:
24542462
24552463
Some values such as the database host name, can be specified as
24562464
:func:`oracledb.create_pool()` parameters, as part of the connect string, and
2457-
in the ``params`` object. If a ``dsn`` is passed, the python-oracledb
2458-
:ref:`Thick <enablingthick>` mode will use the ``dsn`` string to connect.
2459-
Otherwise, a connection string is internally constructed from the individual
2460-
parameters and ``params`` object values, with the individual parameters having
2461-
precedence. In python-oracledb's default Thin mode, a connection string is
2462-
internally used that contains all relevant values specified. The precedence in
2463-
Thin mode is that values in any ``dsn`` parameter override values passed as
2464-
individual parameters, which themselves override values set in the ``params``
2465-
object. Similar precedence rules also apply to other values.
2465+
in the ``params`` object. If a ``dsn`` is passed, a connection string is
2466+
internally constructed from the individual parameters and ``params`` object
2467+
values, with the individual parameters having precedence. The precedence is
2468+
that values in any ``dsn`` parameter override values passed as individual
2469+
parameters, which themselves override values set in the ``params`` object.
2470+
Similar precedence rules also apply to other values.
24662471

24672472
.. _definepoolparams:
24682473

src/oracledb/base_impl.pxd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -500,9 +500,11 @@ cdef class Description(ConnectParamsNode):
500500
public str ssl_server_cert_dn
501501
public object ssl_version
502502
public str wallet_location
503+
dict extra_connect_data_args
503504
str connection_id
504505

505506
cdef str _build_duration_str(self, double value)
507+
cdef str _value_repr(self, object value)
506508
cdef str build_connect_string(self, str cid=*)
507509
cdef int set_server_type(self, str value) except -1
508510

@@ -556,6 +558,7 @@ cdef class ConnectParamsImpl:
556558

557559
cdef int _check_credentials(self) except -1
558560
cdef int _copy(self, ConnectParamsImpl other_params) except -1
561+
cdef str _get_connect_string(self)
559562
cdef bytes _get_new_password(self)
560563
cdef bytearray _get_obfuscator(self, str secret_value)
561564
cdef bytes _get_password(self)

src/oracledb/connection.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -549,7 +549,7 @@ def __init__(
549549
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
550550
else:
551551
params_impl = params._impl.copy()
552-
dsn = params_impl.process_args(dsn, kwargs, thin)
552+
dsn = params_impl.process_args(dsn, kwargs)
553553

554554
# see if connection is being acquired from a pool
555555
if pool is None:
@@ -1545,7 +1545,7 @@ async def _connect(self, dsn, pool, params, kwargs):
15451545
errors._raise_err(errors.ERR_INVALID_CONNECT_PARAMS)
15461546
else:
15471547
params_impl = params._impl.copy()
1548-
dsn = params_impl.process_args(dsn, kwargs, thin=True)
1548+
dsn = params_impl.process_args(dsn, kwargs)
15491549

15501550
# see if connection is being acquired from a pool
15511551
if pool is None:

src/oracledb/impl/base/connect_params.pyx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2022, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2022, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -188,6 +188,12 @@ cdef class ConnectParamsImpl:
188188
self.osuser = other_params.osuser
189189
self.driver_name = other_params.driver_name
190190

191+
cdef str _get_connect_string(self):
192+
"""
193+
Returns the connect string to use for the stored components.
194+
"""
195+
return self.description_list.build_connect_string()
196+
191197
cdef bytes _get_new_password(self):
192198
"""
193199
Returns the new password, after removing the obfuscation.
@@ -434,7 +440,7 @@ cdef class ConnectParamsImpl:
434440
"""
435441
Returns a connect string generated from the parameters.
436442
"""
437-
return self.description_list.build_connect_string()
443+
return self._get_connect_string()
438444

439445
def get_full_user(self):
440446
"""
@@ -511,16 +517,15 @@ cdef class ConnectParamsImpl:
511517
else:
512518
self.user = user
513519

514-
def process_args(self, str dsn, dict kwargs, bint thin):
520+
def process_args(self, str dsn, dict kwargs):
515521
"""
516522
Processes the arguments to connect() and create_pool().
517523
518524
- the keyword arguments are set
519525
- if no user was specified in the keyword arguments and a dsn is
520526
specified, it is parsed to determine the user, password and
521527
connect string and the user and password are stored
522-
- in thin mode, the connect string is then parsed into its
523-
components and stored
528+
- the connect string is then parsed into its components and stored
524529
- if no dsn was specified, one is built from the components
525530
- the connect string is returned
526531
"""
@@ -529,10 +534,10 @@ cdef class ConnectParamsImpl:
529534
if self.user is None and not self.externalauth and dsn is not None:
530535
user, password, dsn = self.parse_dsn_with_credentials(dsn)
531536
self.set(dict(user=user, password=password))
532-
if dsn is not None and thin:
537+
if dsn is not None:
533538
self.parse_connect_string(dsn)
534-
if dsn is None:
535-
dsn = self.get_connect_string()
539+
else:
540+
dsn = self._get_connect_string()
536541
return dsn
537542

538543

@@ -767,6 +772,17 @@ cdef class Description(ConnectParamsNode):
767772
return f"{value_minutes}min"
768773
return f"{value_int}"
769774

775+
cdef str _value_repr(self, object value):
776+
"""
777+
Returns the representation to use for a value. Strings are returned as
778+
is but dictionaries are returned as key/value pairs in the format
779+
expected by the listener.
780+
"""
781+
if isinstance(value, str):
782+
return value
783+
return "".join(f"({k.upper()}={self._value_repr(v)})"
784+
for k, v in value.items())
785+
770786
cdef str build_connect_string(self, str cid=None):
771787
"""
772788
Build a connect string from the components.
@@ -829,6 +845,9 @@ cdef class Description(ConnectParamsNode):
829845
temp_parts.append(f"(POOL_PURITY=SELF)")
830846
elif self.purity == PURITY_NEW:
831847
temp_parts.append(f"(POOL_PURITY=NEW)")
848+
if self.extra_connect_data_args is not None:
849+
temp_parts.extend(f"({k.upper()}={self._value_repr(v)})"
850+
for k, v in self.extra_connect_data_args.items())
832851
if self.connection_id is not None:
833852
temp_parts.append(f"(CONNECTION_ID={self.connection_id})")
834853
if temp_parts:
@@ -904,6 +923,9 @@ cdef class Description(ConnectParamsNode):
904923
_set_str_param(args, "pool_boundary", self)
905924
_set_str_param(args, "connection_id_prefix", self)
906925
_set_bool_param(args, "use_tcp_fast_open", &self.use_tcp_fast_open)
926+
extra_args = args.get("extra_connect_data_args")
927+
if extra_args is not None:
928+
self.extra_connect_data_args = extra_args
907929

908930
def set_from_description_args(self, dict args):
909931
"""

src/oracledb/impl/base/parsers.pyx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,21 @@ CONTAINER_PARAM_NAMES = set([
5050
"security",
5151
])
5252

53-
# a set of parameter names supported in EasyConnect strings that are common
54-
# to all drivers
53+
# CONNECT_DATA parameter names that are supported by the driver; all other
54+
# simple key/value pairs are passed unchanged to the database
55+
CONNECT_DATA_PARAM_NAMES = set([
56+
"cclass",
57+
"connection_id_prefix",
58+
"pool_boundary",
59+
"purity",
60+
"server_type",
61+
"service_name",
62+
"sid",
63+
"use_tcp_fast_open",
64+
])
65+
66+
# a set of parameter names supported by the driver in EasyConnect strings that
67+
# are common to all drivers
5568
COMMON_PARAM_NAMES = set([
5669
"expire_time",
5770
"https_proxy",
@@ -593,6 +606,22 @@ cdef class ConnectStringParser(BaseParser):
593606
value = self.data_as_str[service_name_end_pos + 1:self.temp_pos]
594607
self.description.set_server_type(value)
595608

609+
cdef dict _set_connect_data(self, dict args):
610+
"""
611+
Sets the connect data value.
612+
"""
613+
cdef:
614+
dict extras, result = {}
615+
object value
616+
str key
617+
for key, value in args.items():
618+
if key in CONNECT_DATA_PARAM_NAMES:
619+
result[key] = value
620+
else:
621+
extras = result.setdefault("extra_connect_data_args", {})
622+
extras[key] = value
623+
return result
624+
596625
cdef int _set_descriptor_arg(
597626
self, dict args, str name, object value
598627
) except -1:
@@ -614,6 +643,8 @@ cdef class ConnectStringParser(BaseParser):
614643
if not isinstance(addresses, list):
615644
addresses = [addresses]
616645
value = [dict(address=a) for a in addresses] + [value]
646+
elif name == "connect_data":
647+
value = self._set_connect_data(value)
617648
args[name] = value
618649
elif isinstance(orig_value, list):
619650
args[name].append(value)

0 commit comments

Comments
 (0)