Skip to content

Commit f6aa0bd

Browse files
authored
feat(oracle): correct lookup for JSON supported versions (#247)
Enhance the Oracle data dictionary to correctly identify and handle supported versions for the JSON data type.
1 parent 111d619 commit f6aa0bd

File tree

5 files changed

+458
-403
lines changed

5 files changed

+458
-403
lines changed

sqlspec/adapters/oracledb/data_dictionary.py

Lines changed: 73 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
ORACLE_MIN_OSON_VERSION = 19
2828

2929
# 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
3132

3233
COMPONENT_VERSION_SQL = (
33-
"SELECT product || ' Release ' || version AS \"banner\" "
34+
'SELECT product AS "product", version AS "version", status AS "status" '
3435
"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"
3638
)
3739

3840
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:
137139
ORDER BY column_id
138140
"""
139141

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.
142144
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.
146147
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.
149161
150162
Args:
151-
driver: Database driver instance
163+
driver: Oracle async driver instance.
152164
153165
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.
155184
"""
156-
banner = self._select_version_banner(cast("OracleSyncDriver", driver))
157185

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")
161191

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)
164194
return None
165195

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
170200

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

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)
179208
return version_info
180209

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+
181222
def _get_oracle_compatible(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "str | None":
182223
"""Get Oracle compatible parameter value.
183224
@@ -372,25 +413,12 @@ async def get_version(self, driver: AsyncDriverAdapterBase) -> "OracleVersionInf
372413
Oracle version information or None if detection fails
373414
"""
374415
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)
376418

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:
381420
return None
382421

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
394422
compatible = await self._get_oracle_compatible_async(oracle_driver)
395423
is_autonomous = await self._is_oracle_autonomous_async(oracle_driver)
396424

0 commit comments

Comments
 (0)