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.
@@ -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,3 @@
1
+ import os
2
+
3
+ PACKAGE_PATH = os.path.dirname(__file__)
@@ -0,0 +1,5 @@
1
+ name: dbt_maxcompute
2
+ version: 1.8.0
3
+ config-version: 2
4
+
5
+ macro-paths: ["macros"]
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ dbt