Skip to content

Commit a4511c3

Browse files
authored
Merge pull request #10 from oracle/v1.0.1
Release v1.0.1
2 parents badc33b + d6dd7a8 commit a4511c3

File tree

17 files changed

+240
-45
lines changed

17 files changed

+240
-45
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sudo apt-get update
2+
sudo apt-get install wget libaio1
3+
sudo mkdir -p /opt/oracle
4+
wget https://download.oracle.com/otn_software/linux/instantclient/216000/instantclient-basiclite-linux.x64-21.6.0.0.0dbru.zip -P /tmp
5+
sudo unzip /tmp/instantclient-basiclite-linux.x64-21.6.0.0.0dbru.zip -d /opt/oracle
6+
export PATH="$PATH:/opt/oracle/instantclient_21_6"
7+
export LD_LIBRARY_PATH="/opt/oracle/instantclient_21_6:$LD_LIBRARY_PATH"
8+
sudo mkdir -p /opt/tns_admin
9+
echo "DISABLE_OOB=ON" >> /opt/tns_admin/sqlnet.ora
10+
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
name: dbt-adapter-tests
2+
on: push
3+
4+
jobs:
5+
oracle-21_3_0-xe-test-suite:
6+
runs-on: ${{ matrix.os }}
7+
strategy:
8+
fail-fast: true
9+
matrix:
10+
os: [ ubuntu-20.04 ]
11+
python-version: [ 3.7, 3.8, 3.9 ]
12+
13+
services:
14+
oracle_db_xe:
15+
image: container-registry.oracle.com/database/express:21.3.0-xe
16+
env:
17+
ORACLE_PWD: ${{ secrets.DBT_ORACLE_PASSWORD }}
18+
ports:
19+
- 1521:1521
20+
21+
steps:
22+
- name: Check out dbt-oracle repository code
23+
uses: actions/checkout@v3
24+
25+
- name: Set up Python ${{ matrix.python-version }}
26+
uses: actions/setup-python@v3
27+
with:
28+
python-version: ${{ matrix.python-version }}
29+
30+
- name: Install Oracle Instantclient
31+
run: |
32+
chmod +x ${{ github.workspace }}/.github/scripts/install_oracle_instantclient.sh
33+
${{ github.workspace }}/.github/scripts/install_oracle_instantclient.sh
34+
35+
- name: Install dbt-oracle with core dependencies
36+
run: |
37+
python -m pip install --upgrade pip
38+
pip install pytest pytest-dbt-adapter==0.6.0
39+
pip install -r requirements.txt
40+
pip install -e .
41+
42+
- name: Run adapter tests
43+
run: |
44+
pytest tests/oracle.dbtspec tests/test_config.py
45+
env:
46+
DBT_ORACLE_USER: ${{ secrets.DBT_ORACLE_USER }}
47+
DBT_ORACLE_HOST: localhost
48+
DBT_ORACLE_PORT: 1521
49+
DBT_ORACLE_SCHEMA: ${{ secrets.DBT_ORACLE_USER }}
50+
DBT_ORACLE_PASSWORD: ${{ secrets.DBT_ORACLE_PASSWORD }}
51+
DBT_ORACLE_DATABASE: XEPDB1
52+
DBT_ORACLE_SERVICE: XEPDB1
53+
DBT_ORACLE_PROTOCOL: tcp
54+
LD_LIBRARY_PATH: /opt/oracle/instantclient_21_6
55+
TNS_ADMIN: /opt/tns_admin
56+
57+
oracle-21_3_0-xe-py36-test-suite:
58+
runs-on: ${{ matrix.os }}
59+
strategy:
60+
fail-fast: true
61+
matrix:
62+
os: [ ubuntu-20.04 ]
63+
python-version: [ 3.6 ]
64+
65+
services:
66+
oracle_db_xe:
67+
image: container-registry.oracle.com/database/express:21.3.0-xe
68+
env:
69+
ORACLE_PWD: ${{ secrets.DBT_ORACLE_PASSWORD }}
70+
ports:
71+
- 1521:1521
72+
73+
steps:
74+
- name: Check out dbt-oracle repository code
75+
uses: actions/checkout@v3
76+
77+
- name: Set up Python ${{ matrix.python-version }}
78+
uses: actions/setup-python@v3
79+
with:
80+
python-version: ${{ matrix.python-version }}
81+
82+
- name: Install Oracle Instantclient
83+
run: |
84+
chmod +x ${{ github.workspace }}/.github/scripts/install_oracle_instantclient.sh
85+
${{ github.workspace }}/.github/scripts/install_oracle_instantclient.sh
86+
87+
- name: Install dbt-oracle with core dependencies
88+
run: |
89+
python -m pip install --upgrade pip
90+
pip install pytest pytest-dbt-adapter==0.4.0
91+
pip install -r requirements.txt
92+
pip install -e .
93+
94+
- name: Run adapter tests
95+
run: |
96+
pytest tests/oracle_py36.dbtspec tests/test_config.py
97+
env:
98+
DBT_ORACLE_USER: ${{ secrets.DBT_ORACLE_USER }}
99+
DBT_ORACLE_HOST: localhost
100+
DBT_ORACLE_PORT: 1521
101+
DBT_ORACLE_SCHEMA: ${{ secrets.DBT_ORACLE_USER }}
102+
DBT_ORACLE_PASSWORD: ${{ secrets.DBT_ORACLE_PASSWORD }}
103+
DBT_ORACLE_DATABASE: XEPDB1
104+
DBT_ORACLE_SERVICE: XEPDB1
105+
DBT_ORACLE_PROTOCOL: tcp
106+
LD_LIBRARY_PATH: /opt/oracle/instantclient_21_6
107+
TNS_ADMIN: /opt/tns_admin
108+
109+
110+
111+
112+

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# dbt-oracle
22

3+
[![PyPI version](https://badge.fury.io/py/dbt-oracle.svg)](https://pypi.python.org/pypi/dbt-oracle)
4+
![Build](https://github.com/oracle/dbt-oracle/actions/workflows/oracle-xe-adapter-tests.yml/badge.svg)
5+
36
dbt "adapters" are responsible for adapting dbt's functionality to a given database. `dbt-oracle` implements dbt functionalities for the Oracle database. To learn more about building adapters, check
47
https://docs.getdbt.com/docs/contributing/building-a-new-adapter
58

dbt/adapters/oracle/connections.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
See the License for the specific language governing permissions and
1515
limitations under the License.
1616
"""
17-
from typing import List, Optional, Tuple, Any
17+
from typing import List, Optional, Tuple, Any, Dict
1818
from contextlib import contextmanager
1919
from dataclasses import dataclass, field
2020
import enum
@@ -56,7 +56,8 @@ class OracleAdapterCredentials(Credentials):
5656
# Mandatory required arguments.
5757
user: str
5858
password: str
59-
database: str
59+
# Specifying database is optional
60+
database: Optional[str]
6061

6162
# OracleConnectionMethod.TNS
6263
tns_name: Optional[str] = None
@@ -104,6 +105,14 @@ def _connection_keys(self) -> Tuple[str]:
104105
'cclass', 'purity'
105106
)
106107

108+
@classmethod
109+
def __pre_deserialize__(cls, data: Dict[Any, Any]) -> Dict[Any, Any]:
110+
# If database is not defined as adapter credentials
111+
data = super().__pre_deserialize__(data)
112+
if "database" not in data:
113+
data["database"] = None
114+
return data
115+
107116
def connection_method(self) -> OracleConnectionMethod:
108117
"""Return an OracleConnecitonMethod inferred from the configuration"""
109118
if self.connection_string:
@@ -124,16 +133,8 @@ def get_dsn(self) -> str:
124133
if method == OracleConnectionMethod.CONNECTION_STRING:
125134
return self.connection_string
126135

127-
# Assume host connection method OracleConnectionMethod.HOST
128-
129-
# If the 'service' property is not provided, use 'database' property for
130-
# purposes of connecting.
131-
if self.service:
132-
service = self.service
133-
else:
134-
service = self.database
135-
136-
return f'{self.protocol}://{self.host}:{self.port}/{service}'
136+
# Assume host connection method OracleConnectionMethod.HOST and necessary parameters are defined
137+
return f'{self.protocol}://{self.host}:{self.port}/{self.service}'
137138

138139

139140
class OracleAdapterConnectionManager(SQLConnectionManager):

dbt/adapters/oracle/impl.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@
1515
limitations under the License.
1616
"""
1717
from typing import (
18-
Optional, List
18+
Optional, List, Set
1919
)
20+
from itertools import chain
2021

2122
import dbt.exceptions
23+
from dbt.adapters.base.relation import BaseRelation, InformationSchema
24+
from dbt.adapters.base.impl import GET_CATALOG_MACRO_NAME
2225
from dbt.adapters.sql import SQLAdapter
2326
from dbt.adapters.base.meta import available
2427
from dbt.adapters.oracle import OracleAdapterConnectionManager
2528
from dbt.adapters.oracle.relation import OracleRelation
29+
from dbt.contracts.graph.manifest import Manifest
30+
2631

2732

2833
import agate
@@ -56,6 +61,7 @@
5661
'''.strip()
5762

5863
LIST_RELATIONS_MACRO_NAME = 'list_relations_without_caching'
64+
GET_DATABASE_MACRO_NAME = 'get_database_name'
5965

6066

6167
class OracleAdapter(SQLAdapter):
@@ -103,7 +109,7 @@ def verify_database(self, database):
103109
if database.startswith('"'):
104110
database = database.strip('"')
105111
expected = self.config.credentials.database
106-
if database.lower() != expected.lower():
112+
if expected and database.lower() != expected.lower():
107113
raise dbt.exceptions.NotImplementedException(
108114
'Cross-db references not allowed in {} ({} vs {})'
109115
.format(self.type(), database, expected)
@@ -150,3 +156,59 @@ def timestamp_add_sql(
150156
# '+ interval' syntax used in postgres/redshift is relatively common
151157
# and might even be the SQL standard's intention.
152158
return f"{add_to} + interval '{number}' {interval}"
159+
160+
def get_relation(self, database: str, schema: str, identifier: str) -> Optional[BaseRelation]:
161+
if database == 'None':
162+
database = self.config.credentials.database
163+
return super().get_relation(database, schema, identifier)
164+
165+
def _get_one_catalog(
166+
self,
167+
information_schema: InformationSchema,
168+
schemas: Set[str],
169+
manifest: Manifest,
170+
) -> agate.Table:
171+
172+
kwargs = {"information_schema": information_schema, "schemas": schemas}
173+
table = self.execute_macro(
174+
GET_CATALOG_MACRO_NAME,
175+
kwargs=kwargs,
176+
# pass in the full manifest so we get any local project
177+
# overrides
178+
manifest=manifest,
179+
)
180+
# In case database is not defined, we can use the the configured database which we set as part of credentials
181+
for node in chain(manifest.nodes.values(), manifest.sources.values()):
182+
if not node.database or node.database == 'None':
183+
node.database = self.config.credentials.database
184+
185+
results = self._catalog_filter_table(table, manifest)
186+
return results
187+
188+
def list_relations_without_caching(
189+
self, schema_relation: BaseRelation,
190+
) -> List[BaseRelation]:
191+
192+
# Set database if not supplied
193+
if not self.config.credentials.database:
194+
self.config.credentials.database = self.execute_macro(GET_DATABASE_MACRO_NAME)
195+
196+
kwargs = {'schema_relation': schema_relation}
197+
results = self.execute_macro(
198+
LIST_RELATIONS_MACRO_NAME,
199+
kwargs=kwargs
200+
)
201+
relations = []
202+
for _database, name, _schema, _type in results:
203+
try:
204+
_type = self.Relation.get_relation_type(_type)
205+
except ValueError:
206+
_type = self.Relation.External
207+
relations.append(self.Relation.create(
208+
database=_database,
209+
schema=_schema,
210+
identifier=name,
211+
quote_policy=self.config.quoting,
212+
type=_type
213+
))
214+
return relations

dbt/adapters/oracle/sample_profiles.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,5 @@ default:
99
password: "{{ env_var('DBT_ORACLE_PASSWORD') }}"
1010
port: 1522
1111
service: "{{ env_var('DBT_ORACLE_SERVICE') }}"
12-
dbname: "{{ env_var('DBT_ORACLE_DATABASE') }}"
1312
schema: "{{ env_var('DBT_ORACLE_SCHEMA') }}"
1413
threads: 1
15-
shardingkey:
16-
- skey
17-
supershardingkey:
18-
- sskey
19-
cclass: CONNECTIVITY_CLASS
20-
purity: self|new|default

dbt/include/oracle/macros/adapters.sql

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,6 @@
182182
{% if relation.schema %}
183183
and table_schema = upper('{{ relation.schema }}')
184184
{% endif %}
185-
{% if relation.database %}
186-
and table_catalog = upper('{{ relation.database }}')
187-
{% endif %}
188185
order by ordinal_position
189186

190187
{% endcall %}
@@ -341,7 +338,6 @@
341338
end as "kind"
342339
from tables
343340
where table_type in ('BASE TABLE', 'VIEW')
344-
and table_catalog = upper('{{ schema_relation.database }}')
345341
and table_schema = upper('{{ schema_relation.schema }}')
346342
{% endcall %}
347343
{{ return(load_result('list_relations_without_caching').table) }}
@@ -360,3 +356,9 @@
360356

361357
{% do return(tmp_relation) %}
362358
{% endmacro %}
359+
360+
{% macro get_database_name() %}
361+
{% set results = run_query("select SYS_CONTEXT('userenv', 'DB_NAME') FROM DUAL") %}
362+
{% set db_name = results.columns[0].values()[0] %}
363+
{{ return(db_name) }}
364+
{% endmacro %}

dbt/include/oracle/macros/catalog.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
But we won't fail in the case where there are multiple quoting-difference-only dbs, which is better.
2323
#}
2424
{% set database = information_schema.database %}
25+
{% if database == 'None' or database is undefined or database is none %}
26+
{% set database = get_database_name() %}
27+
{% endif %}
2528
{{ adapter.verify_database(database) }}
2629
2730
with columns as (

dbt/include/oracle/macros/materializations/snapshot/snapshot.sql

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,17 @@
208208
{%- set strategy_name = config.get('strategy') -%}
209209
{%- set unique_key = config.get('unique_key') %}
210210

211-
{% if not adapter.check_schema_exists(model.database, model.schema) %}
212-
{% do create_schema(model.database, model.schema) %}
211+
{% set model_database = model.database %}
212+
{% if model_database == 'None' or model_database is undefined or model_database is none %}
213+
{% set model_database = get_database_name() %}
214+
{% endif %}
215+
216+
{% if not adapter.check_schema_exists(model_database, model.schema) %}
217+
{% do create_schema(model_database, model.schema) %}
213218
{% endif %}
214219

215220
{% set target_relation_exists, target_relation = get_or_create_relation(
216-
database=model.database,
221+
database=model_database,
217222
schema=model.schema,
218223
identifier=target_table,
219224
type='table') -%}

dbt/include/oracle/macros/materializations/snapshot/strategies.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,13 @@
7777
{{ return([false, query_columns]) }}
7878
{%- endif -%}
7979
{# handle any schema changes #}
80+
{% set node_database = node.database %}
81+
{% if node_database == 'None' or model_database is undefined or model_database is none %}
82+
{% set node_database = get_database_name() %}
83+
{% endif %}
8084
{%- set target_table = node.get('alias', node.get('name')) -%}
81-
{%- set target_relation = adapter.get_relation(database=node.database, schema=node.schema, identifier=target_table) -%}
85+
86+
{%- set target_relation = adapter.get_relation(database=node_database, schema=node.schema, identifier=target_table) -%}
8287
{%- set existing_cols = get_columns_in_query('select * from ' ~ target_relation.quote(schema=False, identifier=False)) -%}
8388
{%- set ns = namespace() -%} {# handle for-loop scoping with a namespace #}
8489
{%- set ns.column_added = false -%}

dbt/include/oracle/profile_template.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ prompts:
2121
hint: 'service name in tnsnames.ora'
2222
type: string
2323
default: "{{ env_var('DBT_ORACLE_SERVICE') }}"
24-
dbname:
25-
hint: 'database name in which dbt objects should be created'
26-
type: string
27-
default: "{{ env_var('DBT_ORACLE_DATABASE') }}"
2824
schema:
2925
hint: 'database schema in which dbt objects should be created'
3026
type: string

dbt_adbs_test_project/profiles.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ dbt_test:
99
host: "{{ env_var('DBT_ORACLE_HOST') }}"
1010
port: 1522
1111
service: "{{ env_var('DBT_ORACLE_SERVICE') }}"
12-
database: "{{ env_var('DBT_ORACLE_DATABASE') }}"
12+
#database: "{{ env_var('DBT_ORACLE_DATABASE') }}"
1313
schema: "{{ env_var('DBT_ORACLE_SCHEMA') }}"
1414
shardingkey:
1515
- skey

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = dbt-oracle
3-
version = 1.0.0
3+
version = 1.0.1
44
description = dbt (data build tool) adapter for the Oracle database
55
long_description = file: README.md
66
long_description_content_type = text/markdown

0 commit comments

Comments
 (0)