Skip to content

Commit 998fca9

Browse files
Further work on handling connection string parsing for lesser known
parameters.
1 parent 9b4510e commit 998fca9

File tree

5 files changed

+575
-42
lines changed

5 files changed

+575
-42
lines changed

doc/src/release_notes.rst

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,15 @@ Common Changes
8585
#) Added :meth:`oracledb.register_password_type()` to allow users to register
8686
a function that will be called when a password is supplied as a dictionary
8787
containing the key "type".
88-
#) All connect strings are now parsed by the driver. Previously, only thin
89-
mode parsed all connect strings and thick mode passed the connect string
88+
#) All connect strings are now parsed by the driver. Previously, only Thin
89+
mode parsed all connect strings and Thick mode passed the connect string
9090
unchanged to the Oracle Client library to parse. Parameters unrecognized by
91-
the driver in Easy Connect strings are now ignored. Parameters unrecognized
92-
by the driver in the ``CONNECT_DATA`` section of a full connect descriptor
93-
are passed through unchanged. All other parameters in other sections of a
94-
full connect deescriptor that are unrecognized by the driver are ignored.
91+
the driver in :ref:`Easy Connect strings <easyconnect>` are now ignored.
92+
Parameters unrecognized by the driver in the ``DESCRIPTION``,
93+
``CONNECT_DATA`` and ``SECURITY`` sections of a
94+
:ref:`full connect descriptor <conndescriptor>` are passed through
95+
unchanged. All other parameters in other sections of a full connect
96+
descriptor that are unrecognized by the driver are ignored.
9597
#) Added attributes :attr:`DbObjectAttribute.precision`,
9698
:attr:`DbObjectAttribute.scale`, and :attr:`DbObjectAttribute.max_size` that
9799
provide additional metadata about

src/oracledb/base_impl.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ cdef class ConnectParamsNode:
456456
list active_children
457457

458458
cdef int _copy(self, ConnectParamsNode source) except -1
459+
cdef list _get_initial_connect_string_parts(self)
459460
cdef int _set_active_children(self, list children) except -1
460461

461462

@@ -502,6 +503,8 @@ cdef class Description(ConnectParamsNode):
502503
public object ssl_version
503504
public str wallet_location
504505
dict extra_connect_data_args
506+
dict extra_security_args
507+
dict extra_args
505508
str connection_id
506509

507510
cdef str _build_duration_str(self, double value)

src/oracledb/impl/base/connect_params.pyx

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,20 @@ cdef class ConnectParamsNode:
561561
self.load_balance = source.load_balance
562562
self.source_route = source.source_route
563563

564+
cdef list _get_initial_connect_string_parts(self):
565+
"""
566+
Returns a list of the initial connect strings parts used for container
567+
nodes.
568+
"""
569+
cdef list parts = []
570+
if not self.failover:
571+
parts.append("(FAILOVER=OFF)")
572+
if self.load_balance:
573+
parts.append("(LOAD_BALANCE=ON)")
574+
if self.source_route:
575+
parts.append("(SOURCE_ROUTE=ON)")
576+
return parts
577+
564578
cdef int _set_active_children(self, list children) except -1:
565579
"""
566580
Set the active children to process when connecting to the database.
@@ -731,8 +745,12 @@ cdef class AddressList(ConnectParamsNode):
731745
"""
732746
Build a connect string from the components.
733747
"""
734-
cdef Address a
735-
parts = [a.build_connect_string() for a in self.children]
748+
cdef:
749+
Address address
750+
list parts
751+
parts = self._get_initial_connect_string_parts()
752+
for address in self.children:
753+
parts.append(address.build_connect_string())
736754
if len(parts) == 1:
737755
return parts[0]
738756
return f'(ADDRESS_LIST={"".join(parts)})'
@@ -795,11 +813,7 @@ cdef class Description(ConnectParamsNode):
795813
str temp
796814

797815
# build top-level description parts
798-
parts = []
799-
if self.load_balance:
800-
parts.append("(LOAD_BALANCE=ON)")
801-
if self.source_route:
802-
parts.append("(SOURCE_ROUTE=ON)")
816+
parts = self._get_initial_connect_string_parts()
803817
if self.retry_count != 0:
804818
parts.append(f"(RETRY_COUNT={self.retry_count})")
805819
if self.retry_delay != 0:
@@ -813,6 +827,9 @@ cdef class Description(ConnectParamsNode):
813827
parts.append("(USE_SNI=ON)")
814828
if self.sdu != DEFAULT_SDU:
815829
parts.append(f"(SDU={self.sdu})")
830+
if self.extra_args is not None:
831+
parts.extend(f"({k.upper()}={self._value_repr(v)})"
832+
for k, v in self.extra_args.items())
816833

817834
# add address lists, but if the address list contains only a single
818835
# entry and that entry does not have a host, the other parts aren't
@@ -872,6 +889,9 @@ cdef class Description(ConnectParamsNode):
872889
if self.wallet_location is not None:
873890
temp = f"(MY_WALLET_DIRECTORY={self.wallet_location})"
874891
temp_parts.append(temp)
892+
if self.extra_security_args is not None:
893+
temp_parts.extend(f"({k.upper()}={self._value_repr(v)})"
894+
for k, v in self.extra_security_args.items())
875895
parts.append(f'(SECURITY={"".join(temp_parts)})')
876896

877897
return f'(DESCRIPTION={"".join(parts)})'
@@ -902,6 +922,9 @@ cdef class Description(ConnectParamsNode):
902922
description.ssl_version = self.ssl_version
903923
description.use_sni = self.use_sni
904924
description.wallet_location = self.wallet_location
925+
description.extra_args = self.extra_args
926+
description.extra_connect_data_args = self.extra_connect_data_args
927+
description.extra_security_args = self.extra_security_args
905928
return description
906929

907930
def set_from_args(self, dict args):
@@ -949,6 +972,9 @@ cdef class Description(ConnectParamsNode):
949972
self.sdu = min(max(self.sdu, 512), 2097152) # sanitize SDU
950973
_set_duration_param(args, "tcp_connect_timeout",
951974
&self.tcp_connect_timeout)
975+
extra_args = args.get("extra_args")
976+
if extra_args is not None:
977+
self.extra_args = extra_args
952978

953979
def set_from_security_args(self, dict args):
954980
"""
@@ -959,6 +985,9 @@ cdef class Description(ConnectParamsNode):
959985
_set_str_param(args, "ssl_server_cert_dn", self)
960986
_set_ssl_version_param(args, "ssl_version", self)
961987
_set_str_param(args, "wallet_location", self)
988+
extra_args = args.get("extra_security_args")
989+
if extra_args is not None:
990+
self.extra_security_args = extra_args
962991

963992
cdef int set_server_type(self, str value) except -1:
964993
"""
@@ -985,12 +1014,14 @@ cdef class DescriptionList(ConnectParamsNode):
9851014
Build a connect string from the components.
9861015
"""
9871016
cdef:
988-
Description d
1017+
Description description
9891018
list parts
990-
parts = [d.build_connect_string() for d in self.children]
1019+
parts = self._get_initial_connect_string_parts()
1020+
for description in self.children:
1021+
parts.append(description.build_connect_string())
9911022
if len(parts) == 1:
9921023
return parts[0]
993-
return f'(DESCIPTION_LIST={"".join(parts)})'
1024+
return f'(DESCRIPTION_LIST={"".join(parts)})'
9941025

9951026
cdef list get_addresses(self):
9961027
"""

src/oracledb/impl/base/parsers.pyx

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

53+
# DESCRIPTION parameter names that are supported by the driver; all other
54+
# key/value pairs are passed unchanged to the database
55+
DESCRIPTION_PARAM_NAMES = set([
56+
"address",
57+
"address_list",
58+
"connect_data",
59+
"expire_time",
60+
"failover",
61+
"load_balance",
62+
"source_route",
63+
"retry_count",
64+
"retry_delay",
65+
"sdu",
66+
"tcp_connect_timeout",
67+
"use_sni",
68+
"security",
69+
])
70+
71+
# extra DESCRIPTION parameter names that are passed through when detected in an
72+
# easy connect string
73+
EXTRA_DESCRIPTION_PARAM_NAMES = set([
74+
"enable",
75+
"recv_buf_size",
76+
"send_buf_size"
77+
])
78+
5379
# CONNECT_DATA parameter names that are supported by the driver; all other
54-
# simple key/value pairs are passed unchanged to the database
80+
# key/value pairs are passed unchanged to the database
5581
CONNECT_DATA_PARAM_NAMES = set([
5682
"cclass",
5783
"connection_id_prefix",
@@ -64,18 +90,30 @@ CONNECT_DATA_PARAM_NAMES = set([
6490
"use_tcp_fast_open",
6591
])
6692

93+
# SECURITY parameter names that are supported by the driver; all other
94+
# key/value pairs are passed unchanged to the database
95+
SECURITY_PARAM_NAMES = set([
96+
"ssl_server_cert_dn",
97+
"ssl_server_dn_match",
98+
"ssl_version",
99+
"wallet_location",
100+
])
101+
67102
# a set of parameter names supported by the driver in EasyConnect strings that
68103
# are common to all drivers
69104
COMMON_PARAM_NAMES = set([
70105
"expire_time",
106+
"failover",
71107
"https_proxy",
72108
"https_proxy_port",
109+
"load_balance",
73110
"pool_boundary",
74111
"pool_connection_class",
75112
"pool_purity",
76113
"retry_count",
77114
"retry_delay",
78115
"sdu",
116+
"source_route",
79117
"ssl_server_cert_dn",
80118
"ssl_server_dn_match",
81119
"transport_connect_timeout",
@@ -154,7 +192,9 @@ cdef class BaseParser:
154192
cdef Py_UCS4 ch
155193
while self.temp_pos < self.num_chars:
156194
ch = self.get_current_char()
157-
if not cpython.Py_UNICODE_ISALPHA(ch) and ch != '_' and ch != '.':
195+
if not cpython.Py_UNICODE_ISALPHA(ch) \
196+
and not cpython.Py_UNICODE_ISDIGIT(ch) \
197+
and ch != '_' and ch != '.':
158198
break
159199
self.temp_pos += 1
160200

@@ -444,9 +484,9 @@ cdef class ConnectStringParser(BaseParser):
444484
"""
445485
cdef:
446486
ssize_t start_pos, end_pos = 0
487+
str name, value
447488
Py_UCS4 ch = 0
448489
bint keep
449-
str name
450490

451491
# get parameter name
452492
self.skip_spaces()
@@ -459,7 +499,8 @@ cdef class ConnectStringParser(BaseParser):
459499
name = name[len(EXTENDED_PARAM_PREFIX):]
460500
keep = name in EXTENDED_PARAM_NAMES
461501
else:
462-
keep = name in COMMON_PARAM_NAMES
502+
keep = name in COMMON_PARAM_NAMES \
503+
or name in EXTRA_DESCRIPTION_PARAM_NAMES
463504
name = ALTERNATIVE_PARAM_NAMES.get(name, name)
464505

465506
# look for the equals sign
@@ -495,7 +536,11 @@ cdef class ConnectStringParser(BaseParser):
495536
if end_pos > start_pos and keep:
496537
if self.parameters is None:
497538
self.parameters = {}
498-
self.parameters[name] = self.data_as_str[start_pos:end_pos]
539+
value = self.data_as_str[start_pos:end_pos]
540+
if name in EXTRA_DESCRIPTION_PARAM_NAMES:
541+
self.parameters.setdefault("extra_args", {})[name] = value
542+
else:
543+
self.parameters[name] = value
499544
self.skip_spaces()
500545
self.pos = self.temp_pos
501546

@@ -636,19 +681,23 @@ cdef class ConnectStringParser(BaseParser):
636681
self.data_as_str[self.pos + 1:instance_name_end_pos]
637682
self.pos = self.temp_pos
638683

639-
cdef dict _set_connect_data(self, dict args):
684+
cdef dict _process_args_with_extras(self, dict args, set allowed_names,
685+
str extras_name):
640686
"""
641-
Sets the connect data value.
687+
Processes arguments which contain a set of known attributes and any
688+
number of unknown attributes. The known attributes are left untouched
689+
whereas the unknown ones are put in a separate dictionary with the
690+
given name.
642691
"""
643692
cdef:
644693
dict extras, result = {}
645694
object value
646695
str key
647696
for key, value in args.items():
648-
if key in CONNECT_DATA_PARAM_NAMES:
697+
if key in allowed_names:
649698
result[key] = value
650699
else:
651-
extras = result.setdefault("extra_connect_data_args", {})
700+
extras = result.setdefault(extras_name, {})
652701
extras[key] = value
653702
return result
654703

@@ -663,6 +712,21 @@ cdef class ConnectStringParser(BaseParser):
663712
being added and addresses already exist, those addresses are first
664713
added to the address list before the new value is added.
665714
"""
715+
# process unrecognized parameters, if applicable
716+
if name == "description":
717+
value = self._process_args_with_extras(
718+
value, DESCRIPTION_PARAM_NAMES, "extra_args"
719+
)
720+
elif name == "connect_data":
721+
value = self._process_args_with_extras(
722+
value, CONNECT_DATA_PARAM_NAMES, "extra_connect_data_args"
723+
)
724+
elif name == "security":
725+
value = self._process_args_with_extras(
726+
value, SECURITY_PARAM_NAMES, "extra_security_args"
727+
)
728+
729+
# add value to arguments, creating a list if encountered multiple times
666730
orig_value = args.get(name)
667731
if orig_value is None:
668732
if name == "address" and "address_list" in args:
@@ -673,8 +737,6 @@ cdef class ConnectStringParser(BaseParser):
673737
if not isinstance(addresses, list):
674738
addresses = [addresses]
675739
value = [dict(address=a) for a in addresses] + [value]
676-
elif name == "connect_data":
677-
value = self._set_connect_data(value)
678740
args[name] = value
679741
elif isinstance(orig_value, list):
680742
args[name].append(value)

0 commit comments

Comments
 (0)