Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions elt-common/src/elt_common/sources/sqldatabase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
from abc import abstractmethod
from collections.abc import Callable, Generator, Iterable, Iterator
from typing import NamedTuple
from typing import Any, NamedTuple

import pyarrow as pa
import pyarrow.compute as pc
Expand Down Expand Up @@ -191,6 +191,21 @@ def extractor(watermark, *, _name=name):

yield resource_name, properties

def _columns_for_select(self, table: sa.Table) -> Iterable[Any]:
"""Retrieve columns for selection applying any DB-specific requirements."""
if self._engine.dialect.name == "oracle":
# Apply UTC extraction strictly to Oracle databases to avoid Thin mode DPY-3022 errors
selected_cols = []
for col in table.columns:
col_type_str = str(col.type).upper()
if getattr(col.type, "timezone", False) or "WITH TIME ZONE" in col_type_str:
selected_cols.append(sa.func.sys_extract_utc(col).label(col.name))
else:
selected_cols.append(col)
return selected_cols
else:
return table.columns

def _extract_table(
self,
name: str,
Expand All @@ -205,7 +220,8 @@ def _extract_table(
self._metadata,
autoload_with=self._engine,
)
query = sa.select(table)

Comment thread
bashanlam marked this conversation as resolved.
query = sa.select(*self._columns_for_select(table))
if watermark is not None:
column, max_value = watermark.column, watermark.value
LOGGER.debug(f"Cursor value detected. Limiting query to {column} > {max_value}")
Expand All @@ -214,11 +230,9 @@ def _extract_table(
if query_filter:
query = query_filter(query)

query = query.limit(self.config.row_limit)
if self.config.row_limit:
query = query.limit(self.config.row_limit)

# If all the values in a column are null pyarrow won't know what type
# the column should be, so we need to explicitly create a schema from
# the table
pa_schema = to_pyarrow_schema(table)
result = conn.execution_options(yield_per=self.config.chunk_size).execute(query)

Expand Down
2 changes: 2 additions & 0 deletions elt-common/src/elt_common/sources/sqldatabase/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pyarrow as pa
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from sqlalchemy.dialects.oracle import RAW


def to_pyarrow_schema(table: sa.Table) -> pa.Schema:
Expand Down Expand Up @@ -63,6 +64,7 @@ def _to_pyarrow_field(column: sa.Column) -> pa.Field:
sa.VARCHAR: _SQL_ROOT_TYPES[sa.String],
postgresql.JSON: _SQL_ROOT_TYPES[sa.JSON],
postgresql.JSONB: _SQL_ROOT_TYPES[sa.JSON],
RAW: _SQL_ROOT_TYPES[sa.LargeBinary],
Comment thread
bashanlam marked this conversation as resolved.
}

_SQL_TYPE_MAP = _SQL_ROOT_TYPES | _EXTENDED_SQL_TYPES
Expand Down
36 changes: 36 additions & 0 deletions elt-pipelines/fase/ingest/fase/isisuserdb/isisuserdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import sqlalchemy as sa
from elt_common.extract import ResourceWriteProperties
from elt_common.sources.sqldatabase import (
SqlDatabaseExtract,
SqlDatabaseSourceConfig,
TableInfo,
)


class PipelineOracleConfig(SqlDatabaseSourceConfig):
drivername: str = "oracle+oracledb"
tables: list[str]

@property
def connection_url(self) -> sa.URL:
return sa.URL.create(
drivername=self.drivername,
username=self.username,
password=self.password.get_secret_value() if self.password else None,
host=self.host,
port=self.port,
query={"service_name": self.database},
)


class Extract(SqlDatabaseExtract):
config_cls = PipelineOracleConfig

def table_info(self) -> dict[str, TableInfo | None]:
"""Defines the target tables and their ingestion strategy."""
return {
table_name: TableInfo(
write_properties=ResourceWriteProperties(write_mode="replace")
)
for table_name in self.config.tables
}
5 changes: 5 additions & 0 deletions elt-pipelines/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ dependencies = [
proposal = [
"sqlalchemy[postgresql-psycopgbinary]>=2.0.0",
]

isisuserdb = [
"sqlalchemy[oracle-oracledb]>=2.0.0",
]

statusdisplay = [
"requests>=2.34.2",
]
Expand Down
45 changes: 44 additions & 1 deletion elt-pipelines/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading