Skip to content

dev/1.6.0 #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 15, 2023
Merged
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
5 changes: 2 additions & 3 deletions .github/workflows/oracle-xe-adapter-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
fail-fast: true
matrix:
os: [ ubuntu-latest ]
python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
python-version: [ '3.8', '3.9', '3.10', '3.11' ]

services:
oracle_db_xe:
Expand Down Expand Up @@ -48,10 +48,9 @@ jobs:
- name: Install dbt-oracle with core dependencies
run: |
python -m pip install --upgrade pip
pip install pytest dbt-tests-adapter==1.5.3
pip install pytest dbt-tests-adapter~=1.6
pip install -r requirements.txt
pip install -e .
pip install sqlparse==0.4.3

- name: Check create-pem-from-p12 script is installed in bin
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,5 @@ doc/build.gitbak
.venv1.3/
.venv1.4/
.venv1.5/
.venv1.6/
dbt_adbs_py_test_project
12 changes: 1 addition & 11 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,11 +1 @@
include CONTRIBUTING.rst
include HISTORY.md
include LICENSE
include README.rst
include README.md

recursive-include tests *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]

recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
recursive-include dbt/include *.sql *.yml *.md
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Configuration variables
VERSION=1.5.2
VERSION=1.6.0
PROJ_DIR?=$(shell pwd)
VENV_DIR?=${PROJ_DIR}/.bldenv
BUILD_DIR=${PROJ_DIR}/build
Expand Down
4 changes: 3 additions & 1 deletion dbt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
from pkgutil import extend_path

__path__ = extend_path(__path__, __name__)
2 changes: 1 addition & 1 deletion dbt/adapters/oracle/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
version = "1.5.3"
version = "1.6.0"
7 changes: 4 additions & 3 deletions dbt/adapters/oracle/connection_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import dbt.exceptions
from dbt.events import AdapterLogger

from dbt.ui import warning_tag, yellow
from dbt.ui import warning_tag, yellow, red

logger = AdapterLogger("oracle")

Expand Down Expand Up @@ -104,14 +104,15 @@ class OracleDriverType(str, enum.Enum):
if ORA_PYTHON_DRIVER_TYPE == OracleDriverType.CX_ORACLE:
logger.info("Running in cx mode")
description = (
f"cx_oracle will soon be deprecated, use python-oracledb"
f"cx_oracle is no longer maintained, use python-oracledb"
f"\n\nTo switch to python-oracledb set the environment variable ORA_PYTHON_DRIVER_TYPE=thin "
f"\n\nDefault value of ORA_PYTHON_DRIVER_TYPE will be switched to thin started with v1.7.0 "
f"\n\nRead the guideline here: "
f"https://docs.getdbt.com/reference/warehouse-setups/oracle-setup#configure-the-python-driver-mode"
f"\n\nDocumentation for python-oracledb can be found here: "
f"https://oracle.github.io/python-oracledb/"
)
logger.warning(warning_tag(yellow(description)))
logger.warning(warning_tag(red(description)))
import cx_Oracle as oracledb
elif ORA_PYTHON_DRIVER_TYPE == OracleDriverType.THICK:
import oracledb
Expand Down
3 changes: 3 additions & 0 deletions dbt/adapters/oracle/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ def open(cls, connection):
'dsn': dsn
}

if oracledb.__name__ == "oracledb":
conn_config['connection_id_prefix'] = 'dbt-oracle-'

if credentials.shardingkey:
conn_config['shardingkey'] = credentials.shardingkey

Expand Down
7 changes: 2 additions & 5 deletions dbt/adapters/oracle/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,8 @@ def date_function(cls):

@classmethod
def convert_text_type(cls, agate_table, col_idx):
column = agate_table.columns[col_idx]
lens = (len(d.encode("utf-8")) for d in column.values_without_nulls())
max_len = max(lens) if lens else 64
length = max_len if max_len > 16 else 16
return "varchar2({})".format(length)
# Keep this consistent with STRING/TEXT datatypes mapped to "VARCHAR2(4000)"
return "varchar2(4000)"

@classmethod
def convert_date_type(cls, agate_table, col_idx):
Expand Down
128 changes: 116 additions & 12 deletions dbt/adapters/oracle/relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,136 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from dataclasses import dataclass, field
from typing import Optional

from dbt.adapters.base.relation import BaseRelation, Policy
from dataclasses import dataclass, field

from dbt.adapters.base.relation import BaseRelation
from dbt.adapters.relation_configs import (
RelationConfigBase,
RelationConfigChangeAction,
RelationResults,
)
from dbt.context.providers import RuntimeConfigObject
from dbt.contracts.graph.nodes import ModelNode
from dbt.contracts.relation import RelationType
from dbt.exceptions import DbtRuntimeError

@dataclass
class OracleQuotePolicy(Policy):
database: bool = False
schema: bool = False
identifier: bool = False
from dbt.adapters.oracle.relation_configs import (
OracleMaterializedViewConfig,
OracleRefreshMethodConfigChange,
OracleMaterializedViewConfigChangeset,
OracleRefreshModeConfigChange,
OracleBuildModeConfigChange,
OracleQueryRewriteConfigChange,
OracleQueryConfigChange,
OracleQuotePolicy,
OracleIncludePolicy)

from dbt.events import AdapterLogger

@dataclass
class OracleIncludePolicy(Policy):
database: bool = False
schema: bool = True
identifier: bool = True
logger = AdapterLogger("oracle")


@dataclass(frozen=True, eq=False, repr=False)
class OracleRelation(BaseRelation):
quote_policy: OracleQuotePolicy = field(default_factory=lambda: OracleQuotePolicy())
include_policy: OracleIncludePolicy = field(default_factory=lambda: OracleIncludePolicy())
relation_configs = {
RelationType.MaterializedView.value: OracleMaterializedViewConfig
}

@staticmethod
def add_ephemeral_prefix(name):
return f'dbt__cte__{name}__'

@classmethod
def from_runtime_config(cls, runtime_config: RuntimeConfigObject) -> RelationConfigBase:
model_node: ModelNode = runtime_config.model
relation_type: str = model_node.config.materialized

if relation_config := cls.relation_configs.get(relation_type):
return relation_config.from_model_node(model_node)

raise DbtRuntimeError(
f"from_runtime_config() is not supported for the provided relation type: {relation_type}"
)

@classmethod
def materialized_view_config_changeset(
cls, relation_results: RelationResults,
runtime_config: RuntimeConfigObject) -> Optional[OracleMaterializedViewConfigChangeset]:
config_change_collection = OracleMaterializedViewConfigChangeset()

existing_materialized_view = OracleMaterializedViewConfig.from_relation_results(
relation_results
)
new_materialized_view = OracleMaterializedViewConfig.from_model_node(
runtime_config.model
)

assert isinstance(existing_materialized_view, OracleMaterializedViewConfig)
assert isinstance(new_materialized_view, OracleMaterializedViewConfig)

if new_materialized_view.refresh_method.upper() != existing_materialized_view.refresh_method.upper():
config_change_collection.refresh_method = OracleRefreshMethodConfigChange(
action=RelationConfigChangeAction.alter,
context=new_materialized_view.refresh_method
)

if new_materialized_view.refresh_mode.upper() != existing_materialized_view.refresh_mode.upper():
config_change_collection.refresh_mode = OracleRefreshModeConfigChange(
action=RelationConfigChangeAction.alter,
context=new_materialized_view.refresh_mode
)

if new_materialized_view.build_mode.upper() != existing_materialized_view.build_mode.upper():
config_change_collection.build_mode = OracleBuildModeConfigChange(
action=RelationConfigChangeAction.alter,
context=new_materialized_view.build_mode
)

if new_materialized_view.query_rewrite.upper() != existing_materialized_view.query_rewrite.upper():
config_change_collection.query_rewrite = OracleQueryRewriteConfigChange(
action=RelationConfigChangeAction.alter,
context=new_materialized_view.query_rewrite
)

if new_materialized_view.query.upper() != existing_materialized_view.query.upper():
config_change_collection.query = OracleQueryConfigChange(
action=RelationConfigChangeAction.create,
context=new_materialized_view.query
)

logger.debug(f"Config change collection {config_change_collection}")

if config_change_collection.has_changes:

if config_change_collection.refresh_mode is None:
config_change_collection.refresh_mode = OracleRefreshModeConfigChange(
action=RelationConfigChangeAction.alter,
context=existing_materialized_view.refresh_mode)

if config_change_collection.query_rewrite is None:
config_change_collection.query_rewrite = OracleQueryRewriteConfigChange(
action=RelationConfigChangeAction.alter,
context=existing_materialized_view.query_rewrite)

if config_change_collection.refresh_method is None:
config_change_collection.refresh_method = OracleRefreshMethodConfigChange(
action=RelationConfigChangeAction.alter,
context=existing_materialized_view.refresh_method)

if config_change_collection.build_mode is None:
config_change_collection.build_mode = OracleBuildModeConfigChange(
action=RelationConfigChangeAction.alter,
context=existing_materialized_view.build_mode)

if config_change_collection.query is None:
config_change_collection.query = OracleQueryConfigChange(
action=RelationConfigChangeAction.create,
context=existing_materialized_view.query)

return config_change_collection

return None
28 changes: 28 additions & 0 deletions dbt/adapters/oracle/relation_configs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Copyright (c) 2023, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from dbt.adapters.oracle.relation_configs.materialized_view import (
OracleMaterializedViewConfig,
OracleMaterializedViewConfigChangeset,
OracleBuildModeConfigChange,
OracleRefreshModeConfigChange,
OracleQueryConfigChange,
OracleQueryRewriteConfigChange,
OracleRefreshMethodConfigChange)

from dbt.adapters.oracle.relation_configs.policies import (
OracleQuotePolicy,
OracleIncludePolicy)
86 changes: 86 additions & 0 deletions dbt/adapters/oracle/relation_configs/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""
Copyright (c) 2023, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from dataclasses import dataclass
from typing import Any, Dict, Optional

import agate
from dbt.adapters.base.relation import Policy
from dbt.adapters.relation_configs import (
RelationConfigBase,
RelationResults,
)
from dbt.contracts.graph.nodes import ModelNode
from dbt.contracts.relation import ComponentName

from dbt.adapters.oracle.relation_configs.policies import (
OracleQuotePolicy,
OracleIncludePolicy,
)


@dataclass(frozen=True, eq=True, unsafe_hash=True)
class OracleRelationConfigBase(RelationConfigBase):
"""
This base class implements a few boilerplate methods and provides some light structure for Oracle relations.
"""

@classmethod
def include_policy(cls) -> Policy:
return OracleIncludePolicy()

@classmethod
def quote_policy(cls) -> Policy:
return OracleQuotePolicy()

@classmethod
def from_model_node(cls, model_node: ModelNode):
relation_config = cls.parse_model_node(model_node)
relation = cls.from_dict(relation_config)
return relation

@classmethod
def parse_model_node(cls, model_node: ModelNode) -> Dict[str, Any]:
raise NotImplementedError(
"`parse_model_node()` needs to be implemented on this RelationConfigBase instance"
)

@classmethod
def from_relation_results(cls, relation_results: RelationResults):
relation_config = cls.parse_relation_results(relation_results)
relation = cls.from_dict(relation_config)
return relation

@classmethod
def parse_relation_results(cls, relation_results: RelationResults) -> Dict[str, Any]:
raise NotImplementedError(
"`parse_relation_results()` needs to be implemented on this RelationConfigBase instance"
)

@classmethod
def _render_part(cls, component: ComponentName, value: Optional[str]) -> Optional[str]:
if cls.include_policy().get_part(component) and value:
if cls.quote_policy().get_part(component):
return f'"{value}"'
return value.lower()
return None

@classmethod
def _get_first_row(cls, results: agate.Table) -> agate.Row:
try:
return results.rows[0]
except IndexError:
return agate.Row(values=set())
Loading