@@ -250,32 +250,6 @@ cdef class ConnectParamsImpl:
250250 return self ._xor_bytes(self ._private_key,
251251 self ._private_key_obfuscator).decode()
252252
253- cdef TnsnamesFile _get_tnsnames_file(self ):
254- """
255- Return a tnsnames file, if one is present, or None if one is not. If
256- the file was previously loaded, the modification time is checked and,
257- if unchanged, the previous file is returned.
258- """
259- cdef TnsnamesFile tnsnames_file
260- if self .config_dir is None :
261- errors._raise_err(errors.ERR_NO_CONFIG_DIR)
262- file_name = os.path.join(self .config_dir, " tnsnames.ora" )
263- tnsnames_file = _tnsnames_files.get(self .config_dir)
264- try :
265- stat_info = os.stat(file_name)
266- except :
267- if tnsnames_file is not None :
268- del _tnsnames_files[self .config_dir]
269- errors._raise_err(errors.ERR_TNS_NAMES_FILE_MISSING,
270- config_dir = self .config_dir)
271- if tnsnames_file is not None \
272- and tnsnames_file.mtime == stat_info.st_mtime:
273- return tnsnames_file
274- tnsnames_file = TnsnamesFile(file_name, stat_info.st_mtime)
275- tnsnames_file.read()
276- _tnsnames_files[self .config_dir] = tnsnames_file
277- return tnsnames_file
278-
279253 cdef str _get_token(self ):
280254 """
281255 Returns the token, after removing the obfuscation.
@@ -336,6 +310,7 @@ cdef class ConnectParamsImpl:
336310 """
337311 cdef:
338312 TnsnamesFile tnsnames_file
313+ TnsnamesFileReader reader
339314 Description description
340315 Address address
341316 dict args = {}
@@ -355,7 +330,8 @@ cdef class ConnectParamsImpl:
355330 # otherwise, see if the name is a connect alias in a tnsnames.ora
356331 # configuration file
357332 else :
358- tnsnames_file = self ._get_tnsnames_file()
333+ reader = TnsnamesFileReader()
334+ tnsnames_file = reader.read_tnsnames(self .config_dir)
359335 name = connect_string
360336 connect_string = tnsnames_file.entries.get(name.upper())
361337 if connect_string is None :
@@ -567,8 +543,10 @@ cdef class ConnectParamsImpl:
567543 file found in the configuration directory associated with the
568544 parameters. If no such file exists, an error is raised.
569545 """
570- cdef TnsnamesFile tnsnames_file
571- tnsnames_file = self ._get_tnsnames_file()
546+ cdef:
547+ TnsnamesFileReader reader = TnsnamesFileReader()
548+ TnsnamesFile tnsnames_file
549+ tnsnames_file = reader.read_tnsnames(self .config_dir)
572550 return list (tnsnames_file.entries.keys())
573551
574552 def parse_connect_string (self , str connect_string ):
@@ -1015,22 +993,118 @@ cdef class DescriptionList(ConnectParamsNode):
1015993cdef class TnsnamesFile:
1016994 """
1017995 Internal class used to parse and retain connect descriptor entries found in
1018- a tnsnames.ora file.
996+ a tnsnames.ora file or any included file .
1019997 """
998+ cdef:
999+ str file_name
1000+ int mtime
1001+ dict entries
1002+ set included_files
10201003
1021- def __init__ (self , str file_name , int mtime ):
1004+ def __init__ (self , str file_name ):
10221005 self .file_name = file_name
1023- self .mtime = mtime
1006+ self .clear()
1007+ self ._get_mtime(& self .mtime)
1008+
1009+ cdef int _get_mtime(self , int * mtime) except - 1 :
1010+ """
1011+ Returns the modification time of the file or throws an exception if the
1012+ file cannot be found.
1013+ """
1014+ try :
1015+ mtime[0 ] = os.stat(self .file_name).st_mtime
1016+ except Exception as e:
1017+ errors._raise_err(errors.ERR_MISSING_FILE, str (e),
1018+ file_name = self .file_name)
1019+
1020+ cdef int clear(self ) except - 1 :
1021+ """
1022+ Clear all entries in the file.
1023+ """
10241024 self .entries = {}
1025+ self .included_files = set ()
1026+
1027+ def is_current (self ):
1028+ """
1029+ Returns a boolean indicating if the contents are current or not.
1030+ """
1031+ cdef:
1032+ TnsnamesFile included_file
1033+ int mtime
1034+ self ._get_mtime(& mtime)
1035+ if mtime != self .mtime:
1036+ return False
1037+ for included_file in self .included_files:
1038+ if not included_file.is_current():
1039+ return False
1040+ return True
1041+
1042+
1043+
1044+ cdef class TnsnamesFileReader:
1045+ """
1046+ Internal class used to read a tnsnames.ora file and all of its included
1047+ files.
1048+ """
1049+ cdef:
1050+ TnsnamesFile primary_file
1051+ list files_in_progress
1052+ dict entries
10251053
1026- def read (self ):
1054+ cdef int _add_entry(self , TnsnamesFile tnsnames_file, str name,
1055+ str value) except - 1 :
10271056 """
1028- Read and parse the file and retain the connect descriptors found inside
1029- the file.
1057+ Adds an entry to the file, verifying that the name has not been
1058+ duplicated. An entry is always made in the primary file as well .
10301059 """
1031- with open (self .file_name) as f:
1060+ cdef TnsnamesFile orig_file
1061+ if name in self .entries and value != self .primary_file.entries[name]:
1062+ orig_file = self .entries[name]
1063+ errors._raise_err(errors.ERR_NETWORK_SERVICE_NAME_DIFFERS,
1064+ network_service_name = name,
1065+ new_file_name = tnsnames_file.file_name,
1066+ orig_file_name = orig_file.file_name)
1067+ self .entries[name] = tnsnames_file
1068+ self .primary_file.entries[name] = value
1069+ if tnsnames_file is not self .primary_file:
1070+ tnsnames_file.entries[name] = value
1071+
1072+ cdef TnsnamesFile _get_file(self , file_name):
1073+ """
1074+ Get the file from the cache or read it from the file system.
1075+ """
1076+ cdef TnsnamesFile tnsnames_file
1077+ if file_name in self .files_in_progress:
1078+ errors._raise_err(errors.ERR_IFILE_CYCLE_DETECTED,
1079+ including_file_name = self .files_in_progress[- 1 ],
1080+ included_file_name = file_name)
1081+ tnsnames_file = _tnsnames_files.get(file_name)
1082+ if tnsnames_file is None :
1083+ tnsnames_file = TnsnamesFile(file_name)
1084+ else :
1085+ if tnsnames_file.is_current():
1086+ return tnsnames_file
1087+ del _tnsnames_files[file_name]
1088+ if self .primary_file is None :
1089+ self .primary_file = tnsnames_file
1090+ self .files_in_progress.append(file_name)
1091+ self ._read_file(tnsnames_file)
1092+ _tnsnames_files[file_name] = tnsnames_file
1093+ self .files_in_progress.pop()
1094+ return tnsnames_file
1095+
1096+ cdef int _read_file(self , TnsnamesFile tnsnames_file) except - 1 :
1097+ """
1098+ Reads the file and parses the contents.
1099+ """
1100+ cdef:
1101+ TnsnamesFile included_file
1102+ int line_no = 0
1103+ tnsnames_file.clear()
1104+ with open (tnsnames_file.file_name) as f:
10321105 entry_names = None
10331106 for line in f:
1107+ line_no += 1
10341108 line = line.strip()
10351109 pos = line.find(" #" )
10361110 if pos >= 0 :
@@ -1040,8 +1114,20 @@ cdef class TnsnamesFile:
10401114 if entry_names is None :
10411115 pos = line.find(" =" )
10421116 if pos < 0 :
1117+ errors._raise_err(
1118+ errors.ERR_NETWORK_SERVICE_NAME_INVALID,
1119+ line_no = line_no,
1120+ file_name = tnsnames_file.file_name)
1121+ name = line[:pos].strip().upper()
1122+ if name == " IFILE" :
1123+ file_name = line[pos + 1 :].strip()
1124+ if not os.path.isabs(file_name):
1125+ dir_name = os.path.dirname(tnsnames_file.file_name)
1126+ file_name = os.path.join(dir_name, file_name)
1127+ included_file = self ._get_file(file_name)
1128+ tnsnames_file.included_files.add(included_file)
10431129 continue
1044- entry_names = [s.strip() for s in line[:pos] .split(" ," )]
1130+ entry_names = [s.strip() for s in name .split(" ," )]
10451131 entry_lines = []
10461132 num_parens = 0
10471133 line = line[pos+ 1 :].strip()
@@ -1051,5 +1137,18 @@ cdef class TnsnamesFile:
10511137 if entry_lines and num_parens <= 0 :
10521138 descriptor = " " .join(entry_lines)
10531139 for name in entry_names:
1054- self .entries[name.upper()] = descriptor
1140+ self ._add_entry(tnsnames_file, name, descriptor)
10551141 entry_names = None
1142+
1143+ cdef TnsnamesFile read_tnsnames(self , str dir_name):
1144+ """
1145+ Read the tnsnames.ora file found in the given directory or raise an
1146+ exception if no such file can be found.
1147+ """
1148+ self .primary_file = None
1149+ self .files_in_progress = []
1150+ self .entries = {}
1151+ if dir_name is None :
1152+ errors._raise_err(errors.ERR_NO_CONFIG_DIR)
1153+ file_name = os.path.join(dir_name, " tnsnames.ora" )
1154+ return self ._get_file(file_name)
0 commit comments