dbt-maxcompute 1.8.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbt/adapters/maxcompute/__init__.py +13 -0
- dbt/adapters/maxcompute/__version__.py +1 -0
- dbt/adapters/maxcompute/column.py +69 -0
- dbt/adapters/maxcompute/connections.py +109 -0
- dbt/adapters/maxcompute/context.py +17 -0
- dbt/adapters/maxcompute/impl.py +259 -0
- dbt/adapters/maxcompute/relation.py +118 -0
- dbt/adapters/maxcompute/utils.py +36 -0
- dbt/adapters/maxcompute/wrapper.py +63 -0
- dbt/include/maxcompute/__init__.py +3 -0
- dbt/include/maxcompute/dbt_project.yml +5 -0
- dbt/include/maxcompute/macros/adapters.sql +60 -0
- dbt/include/maxcompute/macros/catalog.sql +32 -0
- dbt/include/maxcompute/profile_template.yml +14 -0
- dbt_maxcompute-1.8.0a0.dist-info/METADATA +67 -0
- dbt_maxcompute-1.8.0a0.dist-info/RECORD +18 -0
- dbt_maxcompute-1.8.0a0.dist-info/WHEEL +5 -0
- dbt_maxcompute-1.8.0a0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dbt.adapters.maxcompute.connections import MaxComputeConnectionManager # noqa
|
|
2
|
+
from dbt.adapters.maxcompute.connections import MaxComputeCredentials
|
|
3
|
+
from dbt.adapters.maxcompute.impl import MaxComputeAdapter
|
|
4
|
+
|
|
5
|
+
from dbt.adapters.base import AdapterPlugin
|
|
6
|
+
from dbt.include import maxcompute
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Plugin = AdapterPlugin(
|
|
10
|
+
adapter=MaxComputeAdapter,
|
|
11
|
+
credentials=MaxComputeCredentials,
|
|
12
|
+
include_path=maxcompute.PACKAGE_PATH
|
|
13
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "1.8.0-alpha"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import TypeVar, Any
|
|
3
|
+
|
|
4
|
+
from dbt.adapters.base.column import Column
|
|
5
|
+
from odps.models.table import TableSchema
|
|
6
|
+
from odps.types import Decimal, Varchar
|
|
7
|
+
|
|
8
|
+
_PARENT_DATA_TYPE_KEY = "__parent_data_type"
|
|
9
|
+
|
|
10
|
+
Self = TypeVar("Self", bound="MaxComputeColumn")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class MaxComputeColumn(Column):
|
|
15
|
+
table_column: TableSchema.TableColumn = None
|
|
16
|
+
comment: str = None
|
|
17
|
+
|
|
18
|
+
TYPE_LABELS = {
|
|
19
|
+
"TEXT": "STRING",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def quoted(self):
|
|
24
|
+
return "`{}`".format(self.column)
|
|
25
|
+
|
|
26
|
+
def literal(self, value):
|
|
27
|
+
return "cast({} as {})".format(value, self.dtype)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def data_type(self) -> str:
|
|
31
|
+
return self.dtype
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def numeric_type(cls, dtype: str, precision: Any, scale: Any) -> str:
|
|
35
|
+
# MaxCompute makes life much harder if precision + scale are specified
|
|
36
|
+
# even if they're fed in here, just return the data type by itself
|
|
37
|
+
return dtype
|
|
38
|
+
|
|
39
|
+
def is_string(self) -> bool:
|
|
40
|
+
return self.dtype.lower() == "string"
|
|
41
|
+
|
|
42
|
+
def can_expand_to(self: Self, other_column: Self) -> bool: # type: ignore
|
|
43
|
+
"""returns True if both columns are strings"""
|
|
44
|
+
return self.is_string() and other_column.is_string()
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
return "<MaxComputeColumn {} ({})>".format(self.name, self.data_type)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_odps_column(cls, column: TableSchema.TableColumn):
|
|
51
|
+
char_size = None
|
|
52
|
+
numeric_precision = None
|
|
53
|
+
numeric_scale = None
|
|
54
|
+
|
|
55
|
+
if isinstance(column.type, Decimal):
|
|
56
|
+
numeric_precision = column.type.precision
|
|
57
|
+
numeric_scale = column.type.scale
|
|
58
|
+
elif isinstance(column.type, Varchar):
|
|
59
|
+
char_size = column.type.size_limit
|
|
60
|
+
|
|
61
|
+
return cls(
|
|
62
|
+
column=column.name,
|
|
63
|
+
dtype=column.type.name,
|
|
64
|
+
char_size=char_size,
|
|
65
|
+
numeric_precision=numeric_precision,
|
|
66
|
+
numeric_scale=numeric_scale,
|
|
67
|
+
table_column=column,
|
|
68
|
+
comment=column.comment,
|
|
69
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
from dbt_common.exceptions import (
|
|
6
|
+
DbtConfigError,
|
|
7
|
+
DbtRuntimeError
|
|
8
|
+
)
|
|
9
|
+
from dbt.adapters.contracts.connection import Credentials, AdapterResponse
|
|
10
|
+
|
|
11
|
+
from dbt.adapters.sql import SQLConnectionManager
|
|
12
|
+
from dbt.adapters.events.logging import AdapterLogger
|
|
13
|
+
|
|
14
|
+
from odps import ODPS
|
|
15
|
+
import odps.dbapi
|
|
16
|
+
|
|
17
|
+
from dbt.adapters.maxcompute.context import set_dbt_default_schema
|
|
18
|
+
from dbt.adapters.maxcompute.wrapper import ConnectionWrapper
|
|
19
|
+
|
|
20
|
+
logger = AdapterLogger("MaxCompute")
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class MaxComputeCredentials(Credentials):
|
|
24
|
+
endpoint: str
|
|
25
|
+
accessId: str
|
|
26
|
+
accessKey: str
|
|
27
|
+
|
|
28
|
+
_ALIASES = {
|
|
29
|
+
'project': 'database',
|
|
30
|
+
'ak': 'accessId',
|
|
31
|
+
'sk': 'accessKey',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def type(self):
|
|
36
|
+
return "maxcompute"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def unique_field(self):
|
|
40
|
+
return self.endpoint + '_' + self.database
|
|
41
|
+
|
|
42
|
+
def _connection_keys(self):
|
|
43
|
+
return ("project", "database")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MaxComputeConnectionManager(SQLConnectionManager):
|
|
47
|
+
TYPE = "maxcompute"
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def open(cls, connection):
|
|
51
|
+
if connection.state == 'open':
|
|
52
|
+
logger.debug('Connection is already open, skipping open.')
|
|
53
|
+
return connection
|
|
54
|
+
|
|
55
|
+
credentials = connection.credentials
|
|
56
|
+
|
|
57
|
+
o = ODPS(
|
|
58
|
+
credentials.accessId,
|
|
59
|
+
credentials.accessKey,
|
|
60
|
+
project=credentials.database,
|
|
61
|
+
endpoint=credentials.endpoint,
|
|
62
|
+
)
|
|
63
|
+
o.schema = credentials.schema
|
|
64
|
+
set_dbt_default_schema(credentials.schema)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
o.get_project().reload()
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise DbtConfigError(f"Failed to connect to MaxCompute: {str(e)}") from e
|
|
70
|
+
|
|
71
|
+
handle = ConnectionWrapper(odps=o, hints={'odps.sql.submit.mode': 'script', 'odps.sql.allow.cartesian':'true'})
|
|
72
|
+
connection.state = 'open'
|
|
73
|
+
connection.handle = handle
|
|
74
|
+
return connection
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def get_response(cls, cursor):
|
|
78
|
+
# FIXME:we should get 'code', 'message', 'rows_affected' from cursor
|
|
79
|
+
logger.info("Current instance id is " + cursor._instance.id)
|
|
80
|
+
return AdapterResponse(_message="OK")
|
|
81
|
+
|
|
82
|
+
def set_query_header(self, query_header_context: Dict[str, Any]) -> None:
|
|
83
|
+
# no query header will better
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
@contextmanager
|
|
87
|
+
def exception_handler(self, sql: str):
|
|
88
|
+
try:
|
|
89
|
+
yield
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
logger.debug("Error while running:\n{}".format(sql))
|
|
92
|
+
logger.debug(exc)
|
|
93
|
+
if len(exc.args) == 0:
|
|
94
|
+
raise
|
|
95
|
+
thrift_resp = exc.args[0]
|
|
96
|
+
if hasattr(thrift_resp, "status"):
|
|
97
|
+
msg = thrift_resp.status.errorMessage
|
|
98
|
+
raise DbtRuntimeError(msg)
|
|
99
|
+
else:
|
|
100
|
+
raise DbtRuntimeError(str(exc))
|
|
101
|
+
|
|
102
|
+
def cancel(self, connection):
|
|
103
|
+
connection.handle.cancel()
|
|
104
|
+
|
|
105
|
+
def add_begin_query(self):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def add_commit_query(self):
|
|
109
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
DBT_DEFAULT_SCHEMA = "__dbt_default__"
|
|
4
|
+
|
|
5
|
+
schema_lock = threading.Lock()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_dbt_default_schema(schema):
|
|
9
|
+
global DBT_DEFAULT_SCHEMA
|
|
10
|
+
with schema_lock:
|
|
11
|
+
DBT_DEFAULT_SCHEMA = schema
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_dbt_default_schema():
|
|
15
|
+
global DBT_DEFAULT_SCHEMA
|
|
16
|
+
with schema_lock:
|
|
17
|
+
return DBT_DEFAULT_SCHEMA
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from multiprocessing.context import SpawnContext
|
|
5
|
+
from typing import Optional, List, Dict, Any
|
|
6
|
+
|
|
7
|
+
import agate
|
|
8
|
+
from dbt.adapters.base import ConstraintSupport
|
|
9
|
+
from dbt.adapters.capability import CapabilityDict, Capability, CapabilitySupport, Support
|
|
10
|
+
from dbt.adapters.contracts.macros import MacroResolverProtocol
|
|
11
|
+
from dbt.adapters.contracts.relation import RelationType
|
|
12
|
+
from dbt.adapters.protocol import AdapterConfig
|
|
13
|
+
from dbt.adapters.sql import SQLAdapter
|
|
14
|
+
from dbt_common.contracts.constraints import ConstraintType
|
|
15
|
+
from dbt_common.utils import AttrDict
|
|
16
|
+
from odps.errors import ODPSError, NoSuchObject
|
|
17
|
+
|
|
18
|
+
from dbt.adapters.maxcompute import MaxComputeConnectionManager
|
|
19
|
+
from dbt.adapters.maxcompute.column import MaxComputeColumn
|
|
20
|
+
from dbt.adapters.maxcompute.relation import MaxComputeRelation
|
|
21
|
+
from dbt.adapters.events.logging import AdapterLogger
|
|
22
|
+
|
|
23
|
+
from dbt.adapters.maxcompute.utils import retry_on_exception
|
|
24
|
+
|
|
25
|
+
logger = AdapterLogger("MaxCompute")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class MaxComputeConfig(AdapterConfig):
|
|
30
|
+
partitionColumns: Optional[List[Dict[str, str]]] = None
|
|
31
|
+
sqlHints: Optional[Dict[str, str]] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MaxComputeAdapter(SQLAdapter):
|
|
35
|
+
RELATION_TYPES = {
|
|
36
|
+
"TABLE": RelationType.Table,
|
|
37
|
+
"VIEW": RelationType.View,
|
|
38
|
+
"MATERIALIZED_VIEW": RelationType.MaterializedView,
|
|
39
|
+
"EXTERNAL": RelationType.External,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ConnectionManager = MaxComputeConnectionManager
|
|
43
|
+
Relation = MaxComputeRelation
|
|
44
|
+
Column = MaxComputeColumn
|
|
45
|
+
AdapterSpecificConfigs = MaxComputeConfig
|
|
46
|
+
|
|
47
|
+
CONSTRAINT_SUPPORT = {
|
|
48
|
+
ConstraintType.check: ConstraintSupport.NOT_SUPPORTED,
|
|
49
|
+
ConstraintType.not_null: ConstraintSupport.ENFORCED,
|
|
50
|
+
ConstraintType.unique: ConstraintSupport.NOT_ENFORCED,
|
|
51
|
+
ConstraintType.primary_key: ConstraintSupport.NOT_ENFORCED,
|
|
52
|
+
ConstraintType.foreign_key: ConstraintSupport.NOT_SUPPORTED,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_capabilities: CapabilityDict = CapabilityDict(
|
|
56
|
+
{
|
|
57
|
+
Capability.TableLastModifiedMetadata: CapabilitySupport(support=Support.Full),
|
|
58
|
+
Capability.SchemaMetadataByRelations: CapabilitySupport(support=Support.Full),
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __init__(self, config, mp_context: SpawnContext) -> None:
|
|
63
|
+
super().__init__(config, mp_context)
|
|
64
|
+
self.connections: MaxComputeConnectionManager = self.connections
|
|
65
|
+
|
|
66
|
+
def get_odps_client(self):
|
|
67
|
+
conn = self.connections.get_thread_connection()
|
|
68
|
+
return conn.handle.odps
|
|
69
|
+
|
|
70
|
+
def get_odps_table_by_relation(self, relation: MaxComputeRelation):
|
|
71
|
+
if self.get_odps_client().exist_table(relation.identifier, relation.project, relation.schema):
|
|
72
|
+
return self.get_odps_client().get_table(relation.identifier, relation.project, relation.schema)
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
@lru_cache(maxsize=100) # Cache results with no limit on size
|
|
76
|
+
def support_namespace_schema(self, project: str):
|
|
77
|
+
return self.get_odps_client().get_project(project).get_property("odps.schema.model.enabled",
|
|
78
|
+
"false") == "true"
|
|
79
|
+
|
|
80
|
+
###
|
|
81
|
+
# Implementations of abstract methods
|
|
82
|
+
###
|
|
83
|
+
@classmethod
|
|
84
|
+
def date_function(cls) -> str:
|
|
85
|
+
return "current_timestamp()"
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def is_cancelable(cls) -> bool:
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def drop_relation(self, relation: MaxComputeRelation) -> None:
|
|
92
|
+
is_cached = self._schema_is_cached(relation.database, relation.schema)
|
|
93
|
+
if is_cached:
|
|
94
|
+
self.cache_dropped(relation)
|
|
95
|
+
conn = self.connections.get_thread_connection()
|
|
96
|
+
conn.handle.odps.delete_table(relation.identifier, relation.project, True, relation.schema)
|
|
97
|
+
|
|
98
|
+
def truncate_relation(self, relation: MaxComputeRelation) -> None:
|
|
99
|
+
# use macro to truncate
|
|
100
|
+
sql = super().truncate_relation(relation)
|
|
101
|
+
logger.info(f"execute sql: {sql}")
|
|
102
|
+
self.get_odps_client().execute_sql(sql)
|
|
103
|
+
|
|
104
|
+
def rename_relation(
|
|
105
|
+
self, from_relation: MaxComputeRelation, to_relation: MaxComputeRelation
|
|
106
|
+
) -> None:
|
|
107
|
+
# use macro to rename
|
|
108
|
+
super().rename_relation(from_relation, to_relation)
|
|
109
|
+
|
|
110
|
+
def get_columns_in_relation(self, relation: MaxComputeRelation):
|
|
111
|
+
logger.info(f"get_columns_in_relation: {relation.render()}")
|
|
112
|
+
odps_table = self.get_odps_table_by_relation(relation)
|
|
113
|
+
return (
|
|
114
|
+
[MaxComputeColumn.from_odps_column(column) for column in odps_table.table_schema.simple_columns]
|
|
115
|
+
if odps_table
|
|
116
|
+
else []
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def execute_macro(
|
|
120
|
+
self,
|
|
121
|
+
macro_name: str,
|
|
122
|
+
macro_resolver: Optional[MacroResolverProtocol] = None,
|
|
123
|
+
project: Optional[str] = None,
|
|
124
|
+
context_override: Optional[Dict[str, Any]] = None,
|
|
125
|
+
kwargs: Optional[Dict[str, Any]] = None,
|
|
126
|
+
needs_conn: bool = False,
|
|
127
|
+
) -> AttrDict:
|
|
128
|
+
sql = super().execute_macro(
|
|
129
|
+
macro_name,
|
|
130
|
+
macro_resolver=macro_resolver,
|
|
131
|
+
project=project,
|
|
132
|
+
context_override=context_override,
|
|
133
|
+
kwargs=kwargs,
|
|
134
|
+
needs_conn=needs_conn
|
|
135
|
+
)
|
|
136
|
+
inst = self.get_odps_client().run_sql(sql=sql)
|
|
137
|
+
logger.info(f"create instance id '{inst.id}', execute_sql: '{sql}'")
|
|
138
|
+
inst.wait_for_success()
|
|
139
|
+
return sql
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def create_schema(self, relation: MaxComputeRelation) -> None:
|
|
144
|
+
logger.info(f"create_schema: '{relation.project}.{relation.schema}'")
|
|
145
|
+
|
|
146
|
+
# Although the odps client has a check schema exist method, it will have a considerable delay,
|
|
147
|
+
# so that it is impossible to judge how many seconds it should wait.
|
|
148
|
+
# The same purpose is achieved by directly deleting and capturing the schema does not exist exception.
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
self.get_odps_client().create_schema(relation.schema, relation.database)
|
|
152
|
+
except ODPSError as e:
|
|
153
|
+
if e.code == "ODPS-0110061":
|
|
154
|
+
return
|
|
155
|
+
else:
|
|
156
|
+
raise e
|
|
157
|
+
# self.checkSchemaDeeply(relation.schema, relation.database, True)
|
|
158
|
+
|
|
159
|
+
def drop_schema(self, relation: MaxComputeRelation) -> None:
|
|
160
|
+
logger.info(f"drop_schema: '{relation.project}.{relation.schema}'")
|
|
161
|
+
|
|
162
|
+
# Although the odps client has a check schema exist method, it will have a considerable delay,
|
|
163
|
+
# so that it is impossible to judge how many seconds it should wait.
|
|
164
|
+
# The same purpose is achieved by directly deleting and capturing the schema does not exist exception.
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
self.get_odps_client().delete_schema(relation.schema, relation.database)
|
|
168
|
+
except ODPSError as e:
|
|
169
|
+
if e.code == "ODPS-0110061":
|
|
170
|
+
return
|
|
171
|
+
else:
|
|
172
|
+
raise e
|
|
173
|
+
# self.checkSchemaDeeply(relation.schema, relation.database, False)
|
|
174
|
+
|
|
175
|
+
def checkSchemaDeeply(self, schema, project, expect_exist):
|
|
176
|
+
logger.info(f"checkSchemaDeeply: {project}.{schema}, expect schema exist: {expect_exist}")
|
|
177
|
+
for retry in range(0, 5):
|
|
178
|
+
try:
|
|
179
|
+
self.get_odps_client().execute_sql(
|
|
180
|
+
f"create or replace view {project}.{schema}.__check_schema_exist__ as select 1;")
|
|
181
|
+
if not expect_exist:
|
|
182
|
+
return
|
|
183
|
+
logger.info(f"{project}.{schema} still exist, continue to wait.")
|
|
184
|
+
self.get_odps_client().execute_sql(
|
|
185
|
+
f"drop view {project}.{schema}.__check_schema_exist__;")
|
|
186
|
+
except ODPSError as e:
|
|
187
|
+
if e.code == "ODPS-0110061":
|
|
188
|
+
if expect_exist:
|
|
189
|
+
return
|
|
190
|
+
logger.info(f"{project}.{schema} not exist, continue to wait.")
|
|
191
|
+
else:
|
|
192
|
+
raise e
|
|
193
|
+
time.sleep(5)
|
|
194
|
+
|
|
195
|
+
def list_relations_without_caching(
|
|
196
|
+
self,
|
|
197
|
+
schema_relation: MaxComputeRelation,
|
|
198
|
+
) -> List[MaxComputeRelation]:
|
|
199
|
+
logger.info(f"list_relations_without_caching: {schema_relation}")
|
|
200
|
+
if not self.check_schema_exists(schema_relation.database, schema_relation.schema):
|
|
201
|
+
return []
|
|
202
|
+
results = self.get_odps_client().list_tables(project=schema_relation.database,
|
|
203
|
+
schema=schema_relation.schema)
|
|
204
|
+
relations = []
|
|
205
|
+
for table in results:
|
|
206
|
+
relations.append(
|
|
207
|
+
MaxComputeRelation.from_odps_table(table)
|
|
208
|
+
)
|
|
209
|
+
return relations
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def quote(cls, identifier):
|
|
213
|
+
return '`{}`'.format(identifier)
|
|
214
|
+
|
|
215
|
+
def list_schemas(self, database: str) -> List[str]:
|
|
216
|
+
database = database.split('.')[0]
|
|
217
|
+
database = database.strip('`')
|
|
218
|
+
if not self.support_namespace_schema(database):
|
|
219
|
+
return ["default"]
|
|
220
|
+
res = [schema.name for schema in self.get_odps_client().list_schemas(database)]
|
|
221
|
+
|
|
222
|
+
logger.info(f"list_schemas: {res}")
|
|
223
|
+
return res
|
|
224
|
+
|
|
225
|
+
def check_schema_exists(self, database: str, schema: str) -> bool:
|
|
226
|
+
database = database.strip('`')
|
|
227
|
+
if not self.support_namespace_schema(database):
|
|
228
|
+
return False
|
|
229
|
+
schema = schema.strip('`')
|
|
230
|
+
time.sleep(10)
|
|
231
|
+
schema_exist = self.get_odps_client().exist_schema(schema, database)
|
|
232
|
+
logger.info(f"check_schema_exists: {database}.{schema}, answer is {schema_exist}")
|
|
233
|
+
return schema_exist
|
|
234
|
+
|
|
235
|
+
# MaxCompute does not support transactions
|
|
236
|
+
def clear_transaction(self) -> None:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def convert_text_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
|
|
241
|
+
return "string"
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def convert_number_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
|
|
245
|
+
return "decimal"
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def convert_integer_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
|
|
249
|
+
return "bigint"
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def convert_datetime_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
|
|
253
|
+
return "timestamp_ntz"
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def convert_time_type(cls, agate_table: "agate.Table", col_idx: int) -> str:
|
|
257
|
+
return "timestamp_ntz"
|
|
258
|
+
|
|
259
|
+
# TODO: standardize_grants_dict method may also be overridden
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import FrozenSet, Optional, TypeVar, Type
|
|
3
|
+
|
|
4
|
+
from dbt.adapters.base.relation import BaseRelation, ComponentName, InformationSchema
|
|
5
|
+
from dbt.adapters.contracts.relation import RelationType, Path, Policy
|
|
6
|
+
from dbt_common.utils.dict import filter_null_values
|
|
7
|
+
from odps.models import Table
|
|
8
|
+
|
|
9
|
+
from dbt.adapters.maxcompute.context import get_dbt_default_schema
|
|
10
|
+
|
|
11
|
+
Self = TypeVar("Self", bound="MaxComputeRelation")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class OdpsIncludePolicy(Policy):
|
|
16
|
+
database: bool = True
|
|
17
|
+
schema: bool = True
|
|
18
|
+
identifier: bool = True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
22
|
+
class MaxComputeRelation(BaseRelation):
|
|
23
|
+
quote_character: str = "`"
|
|
24
|
+
# subquery alias name is not required in MaxCompute
|
|
25
|
+
require_alias: bool = False
|
|
26
|
+
|
|
27
|
+
def without_quote(self):
|
|
28
|
+
return self.quote(False, False, False)
|
|
29
|
+
|
|
30
|
+
include_policy: Policy = field(default_factory=lambda: OdpsIncludePolicy())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
renameable_relations: FrozenSet[RelationType] = field(
|
|
34
|
+
default_factory=lambda: frozenset(
|
|
35
|
+
{
|
|
36
|
+
RelationType.View,
|
|
37
|
+
RelationType.Table,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
replaceable_relations: FrozenSet[RelationType] = field(
|
|
43
|
+
default_factory=lambda: frozenset(
|
|
44
|
+
{
|
|
45
|
+
RelationType.View,
|
|
46
|
+
RelationType.Table,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def schema(self) -> Optional[str]:
|
|
53
|
+
if self.path.schema == "":
|
|
54
|
+
return get_dbt_default_schema()
|
|
55
|
+
return self.path.schema
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def project(self):
|
|
59
|
+
return self.database
|
|
60
|
+
|
|
61
|
+
def information_schema(self, identifier: Optional[str] = None) -> "MaxComputeInformationSchema":
|
|
62
|
+
return MaxComputeInformationSchema.from_relation(self, identifier)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_odps_table(cls, table: Table):
|
|
66
|
+
identifier = table.name
|
|
67
|
+
schema = table.get_schema()
|
|
68
|
+
schema = schema.name if schema else "default"
|
|
69
|
+
|
|
70
|
+
is_view = table.is_virtual_view or table.is_materialized_view
|
|
71
|
+
|
|
72
|
+
return cls.create(
|
|
73
|
+
database=table.project.name,
|
|
74
|
+
schema=table.get_schema().name,
|
|
75
|
+
identifier=identifier,
|
|
76
|
+
type=RelationType.View if is_view else RelationType.Table,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def render(self) -> str:
|
|
80
|
+
render_str = self.project
|
|
81
|
+
if self.schema:
|
|
82
|
+
render_str += "." + self.schema
|
|
83
|
+
if self.table:
|
|
84
|
+
render_str += "." + self.table
|
|
85
|
+
return render_str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
89
|
+
class MaxComputeInformationSchema(InformationSchema):
|
|
90
|
+
quote_character: str = "`"
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def get_path(cls, relation: BaseRelation, information_schema_view: Optional[str]) -> Path:
|
|
94
|
+
return Path(
|
|
95
|
+
database="SYSTEM_CATALOG",
|
|
96
|
+
schema="INFORMATION_SCHEMA",
|
|
97
|
+
identifier=information_schema_view,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def get_include_policy(cls, relation, information_schema_view):
|
|
102
|
+
return relation.include_policy.replace(
|
|
103
|
+
database=True,
|
|
104
|
+
schema=True,
|
|
105
|
+
identifier=True
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def get_quote_policy(
|
|
110
|
+
cls,
|
|
111
|
+
relation,
|
|
112
|
+
information_schema_view: Optional[str],
|
|
113
|
+
) -> Policy:
|
|
114
|
+
return relation.quote_policy.replace(
|
|
115
|
+
database=False,
|
|
116
|
+
schema=False,
|
|
117
|
+
identifier=False
|
|
118
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import functools
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def retry_on_exception(max_retries=3, delay=1, backoff=2, exceptions=(Exception,), condition=None):
|
|
6
|
+
"""
|
|
7
|
+
Decorator for retrying a function if it throws an exception.
|
|
8
|
+
|
|
9
|
+
:param max_retries: Maximum number of retries before giving up.
|
|
10
|
+
:param delay: Initial delay between retries in seconds.
|
|
11
|
+
:param backoff: Multiplier applied to delay between retries.
|
|
12
|
+
:param exceptions: Tuple of exceptions to catch. Defaults to base Exception.
|
|
13
|
+
:param condition: Optional function to determine if the exception should trigger a retry.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def decorator_retry(func):
|
|
17
|
+
@functools.wraps(func)
|
|
18
|
+
def wrapper_retry(*args, **kwargs):
|
|
19
|
+
mtries, mdelay = max_retries, delay
|
|
20
|
+
while mtries > 1:
|
|
21
|
+
try:
|
|
22
|
+
return func(*args, **kwargs)
|
|
23
|
+
except exceptions as ex:
|
|
24
|
+
if condition is not None and not condition(ex):
|
|
25
|
+
raise
|
|
26
|
+
msg = "%s, Retrying in %d seconds..." % (str(ex), mdelay)
|
|
27
|
+
print(msg)
|
|
28
|
+
time.sleep(mdelay)
|
|
29
|
+
mtries -= 1
|
|
30
|
+
mdelay *= backoff
|
|
31
|
+
return func(*args, **kwargs)
|
|
32
|
+
|
|
33
|
+
return wrapper_retry
|
|
34
|
+
|
|
35
|
+
return decorator_retry
|
|
36
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
from dbt.adapters.events.logging import AdapterLogger
|
|
5
|
+
from odps.dbapi import Cursor, Connection
|
|
6
|
+
from odps.errors import ODPSError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConnectionWrapper(Connection):
|
|
10
|
+
|
|
11
|
+
def cursor(self, *args, **kwargs):
|
|
12
|
+
return CursorWrapper(
|
|
13
|
+
self, *args, use_sqa=self._use_sqa,
|
|
14
|
+
fallback_policy=self._fallback_policy,
|
|
15
|
+
hints=self._hints, **kwargs
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
logger = AdapterLogger("MaxCompute")
|
|
19
|
+
|
|
20
|
+
class CursorWrapper(Cursor):
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def execute(self, operation, parameters=None, **kwargs):
|
|
25
|
+
def replace_sql_placeholders(sql_template, values):
|
|
26
|
+
if not values:
|
|
27
|
+
return sql_template
|
|
28
|
+
if operation.count('%s') != len(parameters):
|
|
29
|
+
raise ValueError("参数数量与SQL模板中的占位符数量不匹配")
|
|
30
|
+
return operation % tuple(parameters)
|
|
31
|
+
|
|
32
|
+
def param_normalization(params):
|
|
33
|
+
if not params:
|
|
34
|
+
return None
|
|
35
|
+
normalized_params = []
|
|
36
|
+
for param in params:
|
|
37
|
+
if isinstance(param, Decimal):
|
|
38
|
+
normalized_params.append(f"{param}BD")
|
|
39
|
+
elif isinstance(param, datetime):
|
|
40
|
+
normalized_params.append(f"TIMESTAMP_NTZ'{param.strftime('%Y-%m-%d %H:%M:%S')}'")
|
|
41
|
+
elif isinstance(param, str):
|
|
42
|
+
normalized_params.append(f"'{param}'")
|
|
43
|
+
else:
|
|
44
|
+
normalized_params.append(f"{param}")
|
|
45
|
+
return normalized_params
|
|
46
|
+
|
|
47
|
+
parameters = param_normalization(parameters)
|
|
48
|
+
operation = replace_sql_placeholders(operation, parameters)
|
|
49
|
+
|
|
50
|
+
# retry three times
|
|
51
|
+
for i in range(4):
|
|
52
|
+
try:
|
|
53
|
+
super().execute(operation)
|
|
54
|
+
self._instance.wait_for_success()
|
|
55
|
+
return
|
|
56
|
+
except ODPSError as e:
|
|
57
|
+
# 0130201: view not found, 0110061: table not found
|
|
58
|
+
if e.code == "ODPS-0130201" or e.code == "ODPS-0110061":
|
|
59
|
+
logger.info("retry when execute sql: %s, error: %s", operation, e)
|
|
60
|
+
continue
|
|
61
|
+
else:
|
|
62
|
+
raise e
|
|
63
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* For examples of how to fill out the macros please refer to the postgres adapter and docs
|
|
2
|
+
postgres adapter macros: https://github.com/dbt-labs/dbt-core/blob/main/plugins/postgres/dbt/include/postgres/macros/adapters.sql
|
|
3
|
+
dbt docs: https://docs.getdbt.com/docs/contributing/building-a-new-adapter
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
{% macro maxcompute__truncate_relation(relation) -%}
|
|
7
|
+
{% if relation.schema -%}
|
|
8
|
+
truncate table {{ relation.database }}.{{ relation.schema }}.{{ relation.identifier }};
|
|
9
|
+
{%- else -%}
|
|
10
|
+
truncate table {{ relation.database }}.{{ relation.identifier }};
|
|
11
|
+
{%- endif -%}
|
|
12
|
+
{% endmacro %}
|
|
13
|
+
|
|
14
|
+
{% macro maxcompute__rename_relation(from_relation, to_relation) -%}
|
|
15
|
+
{% if from_relation.schema -%}
|
|
16
|
+
{% if from_relation.is_view -%}
|
|
17
|
+
alter view {{ from_relation.database }}.{{ from_relation.schema }}.{{ from_relation.identifier }} rename to {{ to_relation.identifier }};
|
|
18
|
+
{%- else -%}
|
|
19
|
+
alter table {{ from_relation.database }}.{{ from_relation.schema }}.{{ from_relation.identifier }} rename to {{ to_relation.identifier }};
|
|
20
|
+
{%- endif -%}
|
|
21
|
+
{%- else -%}
|
|
22
|
+
{% if from_relation.is_view -%}
|
|
23
|
+
alter view {{ from_relation.database }}.{{ from_relation.identifier }} rename to {{ to_relation.identifier }}
|
|
24
|
+
{%- else -%}
|
|
25
|
+
alter table {{ from_relation.database }}.{{ from_relation.identifier }} rename to {{ to_relation.identifier }};
|
|
26
|
+
{%- endif -%}
|
|
27
|
+
{%- endif -%}
|
|
28
|
+
{% endmacro %}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
{% macro maxcompute__alter_column_type(relation,column_name,new_column_type) -%}
|
|
32
|
+
{% if relation.schema -%}
|
|
33
|
+
alter table {{ relation.database }}.{{ relation.schema }}.{{ relation.identifier }} change {{ column_name }} {{ column_name }} {{ new_column_type }};
|
|
34
|
+
{%- else -%}
|
|
35
|
+
alter table {{ relation.database }}.{{ relation.identifier }} change {{ column_name }} {{ column_name }} {{ new_column_type }};
|
|
36
|
+
{%- endif -%}
|
|
37
|
+
{% endmacro %}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
{% macro maxcompute__copy_grants() -%}
|
|
41
|
+
{{ return(True) }}
|
|
42
|
+
{% endmacro %}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
{% macro maxcompute__create_table_as(relation, sql) -%}
|
|
46
|
+
{% if relation.schema -%}
|
|
47
|
+
create table if not exists {{ relation.database }}.{{ relation.schema }}.{{ relation.identifier }} as ({{ sql }});
|
|
48
|
+
{%- else -%}
|
|
49
|
+
create table if not exists {{ relation.database }}.default.{{ relation.identifier }} as ({{ sql }});
|
|
50
|
+
{%- endif -%}
|
|
51
|
+
{%- endmacro %}
|
|
52
|
+
|
|
53
|
+
{% macro maxcompute__create_view_as(relation, sql) -%}
|
|
54
|
+
{% if relation.schema -%}
|
|
55
|
+
create or replace view {{ relation.database }}.{{ relation.schema }}.{{ relation.identifier }} as ({{ sql }});
|
|
56
|
+
{%- else -%}
|
|
57
|
+
create or replace view {{ relation.database }}.default.{{ relation.identifier }} as ({{ sql }});
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
{%- endmacro %}
|
|
60
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{{% macro maxcompute__get_catalog(information_schema, schemas)-%}}
|
|
2
|
+
|
|
3
|
+
{{%set msg -%}}
|
|
4
|
+
get_catalog not implemented for maxcompute
|
|
5
|
+
-%}} endset {{%
|
|
6
|
+
/*
|
|
7
|
+
Your database likely has a way of accessing metadata about its objects,
|
|
8
|
+
whether by querying an information schema or by running `show` and `describe` commands.
|
|
9
|
+
dbt will use this macro to generate its catalog of objects it knows about. The catalog is one of
|
|
10
|
+
the artifacts powering the documentation site.
|
|
11
|
+
As an example, below is a simplified version of postgres__get_catalog
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
|
|
16
|
+
select {{database}} as TABLE,
|
|
17
|
+
"- set table type -"
|
|
18
|
+
when 'v' then 'VIEW'
|
|
19
|
+
else 'BASE TABLE'
|
|
20
|
+
"- set table/view names and descriptions -"
|
|
21
|
+
use several joins and search types for pulling info together, sorting etc..
|
|
22
|
+
where (
|
|
23
|
+
search if schema exists, else build
|
|
24
|
+
{%- for schema in schemas -%}
|
|
25
|
+
upper(sch.nspname) = upper('{{ schema }}'){%- if not loop.last %} or {% endif -%}
|
|
26
|
+
{%- endfor -%}
|
|
27
|
+
)
|
|
28
|
+
define any shortcut keys
|
|
29
|
+
|
|
30
|
+
*/
|
|
31
|
+
{{{{ exceptions.raise_compiler_error(msg) }}}}
|
|
32
|
+
{{% endmacro %}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
fixed:
|
|
2
|
+
type: maxcompute
|
|
3
|
+
prompts:
|
|
4
|
+
project:
|
|
5
|
+
hint: "Enter your MaxCompute project name"
|
|
6
|
+
schema:
|
|
7
|
+
hint: "Enter your MaxCompute schema name"
|
|
8
|
+
endpoint:
|
|
9
|
+
hint: "Enter your MaxCompute endpoint URL"
|
|
10
|
+
accessId:
|
|
11
|
+
hint: "Enter your accessId for MaxCompute"
|
|
12
|
+
accessKey:
|
|
13
|
+
hint: "Enter your accessKey for MaxCompute"
|
|
14
|
+
hide_input: true
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dbt-maxcompute
|
|
3
|
+
Version: 1.8.0a0
|
|
4
|
+
Summary: The MaxCompute adapter plugin for dbt
|
|
5
|
+
Home-page: https://github.com/dingxin-tech/dbt-maxcompute
|
|
6
|
+
Author: Alibaba Cloud MaxCompute Team
|
|
7
|
+
Author-email: zhangdingxin.zdx@alibaba-inc.com
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: dbt-common<2.0,>=1.0.4
|
|
21
|
+
Requires-Dist: dbt-adapters<2.0,>=1.1.1
|
|
22
|
+
Requires-Dist: pyodps
|
|
23
|
+
Requires-Dist: dbt-core>=1.8.0
|
|
24
|
+
|
|
25
|
+
v<p align="center">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/dbt-labs/dbt/ec7dee39f793aa4f7dd3dae37282cc87664813e4/etc/dbt-logo-full.svg" alt="dbt logo" width="500"/>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
**[dbt](https://www.getdbt.com/)** enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.
|
|
30
|
+
|
|
31
|
+
dbt is the T in ELT. Organize, cleanse, denormalize, filter, rename, and pre-aggregate the raw data in your warehouse so that it's ready for analysis.
|
|
32
|
+
|
|
33
|
+
## MaxCompute
|
|
34
|
+
This repo contains the base code to help you start to build out your dbt-maxcompute adapter plugin, for more information on how to build out the adapter please follow the [docs](https://docs.getdbt.com/docs/contributing/building-a-new-adapter)
|
|
35
|
+
|
|
36
|
+
** Note ** this `README` is meant to be replaced with what information would be required to use your adpater once your at a point todo so.
|
|
37
|
+
|
|
38
|
+
** Note **
|
|
39
|
+
### Adapter Scaffold default Versioning
|
|
40
|
+
This adapter plugin follows [semantic versioning](https://semver.org/). The first version of this plugin is v1.7.0, in order to be compatible with dbt Core v1.7.0.
|
|
41
|
+
|
|
42
|
+
It's also brand new! For MaxCompute-specific functionality, we will aim for backwards-compatibility wherever possible. We are likely to be iterating more quickly than most major-version-1 software projects. To that end, backwards-incompatible changes will be clearly communicated and limited to minor versions (once every three months).
|
|
43
|
+
|
|
44
|
+
## Getting Started
|
|
45
|
+
|
|
46
|
+
#### Setting up Locally
|
|
47
|
+
- run `pip install -r dev-requirements.txt`.
|
|
48
|
+
- cd directory into the `dbt-core` you'd like to be testing against and run `make dev`.
|
|
49
|
+
|
|
50
|
+
#### Connect to Github
|
|
51
|
+
- run `git init`.
|
|
52
|
+
- Connect your lcoal code to a Github repo.
|
|
53
|
+
|
|
54
|
+
## Join the dbt Community
|
|
55
|
+
|
|
56
|
+
- Be part of the conversation in the [dbt Community Slack](http://community.getdbt.com/)
|
|
57
|
+
- If one doesn't exist feel free to request a #db-MaxCompute channel be made in the [#channel-requests](https://getdbt.slack.com/archives/C01D8J8AJDA) on dbt community slack channel.
|
|
58
|
+
- Read more on the [dbt Community Discourse](https://discourse.getdbt.com)
|
|
59
|
+
|
|
60
|
+
## Reporting bugs and contributing code
|
|
61
|
+
|
|
62
|
+
- Want to report a bug or request a feature? Let us know on [Slack](http://community.getdbt.com/), or open [an issue](https://github.com/dbt-labs/dbt-redshift/issues/new)
|
|
63
|
+
- Want to help us build dbt? Check out the [Contributing Guide](https://github.com/dbt-labs/dbt/blob/HEAD/CONTRIBUTING.md)
|
|
64
|
+
|
|
65
|
+
## Code of Conduct
|
|
66
|
+
|
|
67
|
+
Everyone interacting in the dbt project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [dbt Code of Conduct](https://community.getdbt.com/code-of-conduct).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
dbt/adapters/maxcompute/__init__.py,sha256=dOzJefbPsSIjnwrT3SBcU6e0u0p96dIsv9Z1dxl-_80,435
|
|
2
|
+
dbt/adapters/maxcompute/__version__.py,sha256=xM40SuOoM9AJfLTVSWDAmrxWWVMuif9OhwoYDgF0AVU,24
|
|
3
|
+
dbt/adapters/maxcompute/column.py,sha256=Ui89ZcOMVp8Z-_Nzg4kQ-2rUtD3k-B5i6NWpXO8x6I8,2054
|
|
4
|
+
dbt/adapters/maxcompute/connections.py,sha256=jap7HGUPKG3hz1uypK4TL9P-s_GZmmFYZ2qKvpGYIq0,3055
|
|
5
|
+
dbt/adapters/maxcompute/context.py,sha256=ta4hzT_uQ-GkalINXSuzk1SDsfkJdERLgZv1PLqHhLM,333
|
|
6
|
+
dbt/adapters/maxcompute/impl.py,sha256=8JMTYGlkkNkOSmVkr9z8qyQu4L97rX7_HptVy0eGMJU,10203
|
|
7
|
+
dbt/adapters/maxcompute/relation.py,sha256=YmBOLo-ElSam4sQO3HJvH5LPU7WM8Iuwf5m-gjmJjh0,3420
|
|
8
|
+
dbt/adapters/maxcompute/utils.py,sha256=4gm7dQC0HmrtiP9qhayQ5sU3rOBykNZKWeLEEwcB29o,1293
|
|
9
|
+
dbt/adapters/maxcompute/wrapper.py,sha256=VVxba2UQOCK5r8PIqVT3zCN3Err1GlJK7PsqZlHPw8U,2196
|
|
10
|
+
dbt/include/maxcompute/__init__.py,sha256=vBGWeG-dHHkimfnX8axBJ4IgAowFw8xADmo6Auzn2xc,52
|
|
11
|
+
dbt/include/maxcompute/dbt_project.yml,sha256=Zy2C6E5NH79TU4RSbsprrGaIuQStdhN8KI0p8hoRj-w,79
|
|
12
|
+
dbt/include/maxcompute/profile_template.yml,sha256=RiKzLQrzByTbKssmOVCi7uCu1aSrMyprKl1ab7Gp6dE,349
|
|
13
|
+
dbt/include/maxcompute/macros/adapters.sql,sha256=nrqQJz4yDFB54govWVdqftSkGgcuJb6i5erCqcOLWqY,2601
|
|
14
|
+
dbt/include/maxcompute/macros/catalog.sql,sha256=0t0ud_E98iKQ0dNK3p1DMJ8GiksQPHSQ4yhjFdLM_Ng,1184
|
|
15
|
+
dbt_maxcompute-1.8.0a0.dist-info/METADATA,sha256=rFtkNJHvRP8kxfMrap1--Fd06b_eco3c9JtZf5lbSxs,3613
|
|
16
|
+
dbt_maxcompute-1.8.0a0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
17
|
+
dbt_maxcompute-1.8.0a0.dist-info/top_level.txt,sha256=B2YH4he17ajilEWOGCKHbRcEJlCuZKwCcgFcLPntLsE,4
|
|
18
|
+
dbt_maxcompute-1.8.0a0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dbt
|