|
27 | 27 | ORACLE_MIN_OSON_VERSION = 19 |
28 | 28 |
|
29 | 29 | # Compiled regex patterns |
30 | | -ORACLE_VERSION_PATTERN = re.compile(r"Oracle Database (\d+)c?.* Release (\d+)\.(\d+)\.(\d+)") |
| 30 | +VERSION_NUMBER_PATTERN = re.compile(r"(\d+)") |
| 31 | +VERSION_COMPONENT_COUNT = 3 |
31 | 32 |
|
32 | 33 | COMPONENT_VERSION_SQL = ( |
33 | | - "SELECT product || ' Release ' || version AS \"banner\" " |
| 34 | + 'SELECT product AS "product", version AS "version", status AS "status" ' |
34 | 35 | "FROM product_component_version WHERE product LIKE 'Oracle%' " |
35 | | - "ORDER BY version DESC FETCH FIRST 1 ROWS ONLY" |
| 36 | + "ORDER BY TO_NUMBER(REGEXP_SUBSTR(version, '^[0-9]+')) DESC, version DESC " |
| 37 | + "FETCH FIRST 1 ROWS ONLY" |
36 | 38 | ) |
37 | 39 |
|
38 | 40 | AUTONOMOUS_SERVICE_SQL = "SELECT sys_context('USERENV','CLOUD_SERVICE') AS \"service\" FROM dual" |
@@ -137,47 +139,86 @@ def _get_columns_sql(self, table: str, schema: "str | None" = None) -> str: |
137 | 139 | ORDER BY column_id |
138 | 140 | """ |
139 | 141 |
|
140 | | - def _select_version_banner(self, driver: "OracleSyncDriver") -> str: |
141 | | - return str(driver.select_value(COMPONENT_VERSION_SQL)) |
| 142 | + def _select_component_version_row(self, driver: "OracleSyncDriver") -> "dict[str, Any] | None": |
| 143 | + """Fetch the latest Oracle component version row. |
142 | 144 |
|
143 | | - async def _select_version_banner_async(self, driver: "OracleAsyncDriver") -> str: |
144 | | - result = await driver.select_value(COMPONENT_VERSION_SQL) |
145 | | - return str(result) |
| 145 | + Args: |
| 146 | + driver: Oracle sync driver instance. |
146 | 147 |
|
147 | | - def _get_oracle_version(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "OracleVersionInfo | None": |
148 | | - """Get Oracle database version information. |
| 148 | + Returns: |
| 149 | + First matching row from product_component_version or None. |
| 150 | + """ |
| 151 | + |
| 152 | + result = driver.execute(COMPONENT_VERSION_SQL) |
| 153 | + data = result.get_data() |
| 154 | + if not data: |
| 155 | + logger.warning("No rows returned from product_component_version") |
| 156 | + return None |
| 157 | + return data[0] |
| 158 | + |
| 159 | + async def _select_component_version_row_async(self, driver: "OracleAsyncDriver") -> "dict[str, Any] | None": |
| 160 | + """Async helper to fetch the latest Oracle component version row. |
149 | 161 |
|
150 | 162 | Args: |
151 | | - driver: Database driver instance |
| 163 | + driver: Oracle async driver instance. |
152 | 164 |
|
153 | 165 | Returns: |
154 | | - Oracle version information or None if detection fails |
| 166 | + First matching row from product_component_version or None. |
| 167 | + """ |
| 168 | + |
| 169 | + result = await driver.execute(COMPONENT_VERSION_SQL) |
| 170 | + data = result.get_data() |
| 171 | + if not data: |
| 172 | + logger.warning("No rows returned from product_component_version") |
| 173 | + return None |
| 174 | + return data[0] |
| 175 | + |
| 176 | + def _build_version_info_from_row(self, row: "dict[str, Any] | None") -> "OracleVersionInfo | None": |
| 177 | + """Build Oracle version metadata from a component version row. |
| 178 | +
|
| 179 | + Args: |
| 180 | + row: Data dictionary row containing product/version fields. |
| 181 | +
|
| 182 | + Returns: |
| 183 | + OracleVersionInfo if parsing succeeds, otherwise None. |
155 | 184 | """ |
156 | | - banner = self._select_version_banner(cast("OracleSyncDriver", driver)) |
157 | 185 |
|
158 | | - # Parse version from banner like "Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production" |
159 | | - # or "Oracle Database 19c Standard Edition 2 Release 19.0.0.0.0 - Production" |
160 | | - version_match = ORACLE_VERSION_PATTERN.search(str(banner)) |
| 186 | + if not row: |
| 187 | + logger.warning("Unable to determine Oracle version without component data") |
| 188 | + return None |
| 189 | + |
| 190 | + version_value = row.get("version_full") or row.get("VERSION_FULL") or row.get("version") or row.get("VERSION") |
161 | 191 |
|
162 | | - if not version_match: |
163 | | - logger.warning("Could not parse Oracle version from banner: %s", banner) |
| 192 | + if version_value is None: |
| 193 | + logger.warning("Component version row missing VERSION column: %s", row) |
164 | 194 | return None |
165 | 195 |
|
166 | | - major = int(version_match.group(1)) |
167 | | - release_major = int(version_match.group(2)) |
168 | | - minor = int(version_match.group(3)) |
169 | | - patch = int(version_match.group(4)) |
| 196 | + matches = VERSION_NUMBER_PATTERN.findall(str(version_value)) |
| 197 | + if not matches: |
| 198 | + logger.warning("Unable to parse Oracle version from value: %s", version_value) |
| 199 | + return None |
170 | 200 |
|
171 | | - # For Oracle 21c+, the major version is in the first group |
172 | | - # For Oracle 19c and earlier, use the release version |
173 | | - if major >= ORACLE_MIN_JSON_NATIVE_VERSION: |
174 | | - version_info = OracleVersionInfo(major, minor, patch) |
175 | | - else: |
176 | | - version_info = OracleVersionInfo(release_major, minor, patch) |
| 201 | + numbers = [int(match) for match in matches[:VERSION_COMPONENT_COUNT]] |
| 202 | + while len(numbers) < VERSION_COMPONENT_COUNT: |
| 203 | + numbers.append(0) |
177 | 204 |
|
178 | | - logger.debug("Detected Oracle version: %s", version_info) |
| 205 | + version_info = OracleVersionInfo(numbers[0], numbers[1], numbers[2]) |
| 206 | + product_name = row.get("product") or row.get("PRODUCT") or "Oracle Database" |
| 207 | + logger.debug("Detected Oracle component version for %s: %s", product_name, version_info) |
179 | 208 | return version_info |
180 | 209 |
|
| 210 | + def _get_oracle_version(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "OracleVersionInfo | None": |
| 211 | + """Get Oracle database version information. |
| 212 | +
|
| 213 | + Args: |
| 214 | + driver: Database driver instance |
| 215 | +
|
| 216 | + Returns: |
| 217 | + Oracle version information or None if detection fails |
| 218 | + """ |
| 219 | + row = self._select_component_version_row(cast("OracleSyncDriver", driver)) |
| 220 | + return self._build_version_info_from_row(row) |
| 221 | + |
181 | 222 | def _get_oracle_compatible(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "str | None": |
182 | 223 | """Get Oracle compatible parameter value. |
183 | 224 |
|
@@ -372,25 +413,12 @@ async def get_version(self, driver: AsyncDriverAdapterBase) -> "OracleVersionInf |
372 | 413 | Oracle version information or None if detection fails |
373 | 414 | """ |
374 | 415 | oracle_driver = cast("OracleAsyncDriver", driver) |
375 | | - banner = await self._select_version_banner_async(oracle_driver) |
| 416 | + row = await self._select_component_version_row_async(oracle_driver) |
| 417 | + version_info = self._build_version_info_from_row(row) |
376 | 418 |
|
377 | | - version_match = ORACLE_VERSION_PATTERN.search(str(banner)) |
378 | | - |
379 | | - if not version_match: |
380 | | - logger.warning("Could not parse Oracle version from banner: %s", banner) |
| 419 | + if not version_info: |
381 | 420 | return None |
382 | 421 |
|
383 | | - major = int(version_match.group(1)) |
384 | | - release_major = int(version_match.group(2)) |
385 | | - minor = int(version_match.group(3)) |
386 | | - patch = int(version_match.group(4)) |
387 | | - |
388 | | - if major >= ORACLE_MIN_JSON_NATIVE_VERSION: |
389 | | - version_info = OracleVersionInfo(major, minor, patch) |
390 | | - else: |
391 | | - version_info = OracleVersionInfo(release_major, minor, patch) |
392 | | - |
393 | | - # Enhance with additional information |
394 | 422 | compatible = await self._get_oracle_compatible_async(oracle_driver) |
395 | 423 | is_autonomous = await self._is_oracle_autonomous_async(oracle_driver) |
396 | 424 |
|
|
0 commit comments