dbt-for-apache-doris 1.1.0__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.
Files changed (36) hide show
  1. dbt/adapters/doris/__init__.py +33 -0
  2. dbt/adapters/doris/__version__.py +25 -0
  3. dbt/adapters/doris/column.py +73 -0
  4. dbt/adapters/doris/connections.py +269 -0
  5. dbt/adapters/doris/doris_column_item.py +54 -0
  6. dbt/adapters/doris/impl.py +515 -0
  7. dbt/adapters/doris/relation.py +84 -0
  8. dbt/include/doris/__init__.py +23 -0
  9. dbt/include/doris/dbt_project.yml +24 -0
  10. dbt/include/doris/macros/adapters/columns.sql +341 -0
  11. dbt/include/doris/macros/adapters/freshness.sql +23 -0
  12. dbt/include/doris/macros/adapters/grants.sql +138 -0
  13. dbt/include/doris/macros/adapters/metadata.sql +113 -0
  14. dbt/include/doris/macros/adapters/relation.sql +269 -0
  15. dbt/include/doris/macros/adapters/schema.sql +32 -0
  16. dbt/include/doris/macros/adapters/unit_testing.sql +56 -0
  17. dbt/include/doris/macros/materializations/incremental/help.sql +1060 -0
  18. dbt/include/doris/macros/materializations/incremental/incremental.sql +672 -0
  19. dbt/include/doris/macros/materializations/incremental/strategies.sql +91 -0
  20. dbt/include/doris/macros/materializations/materialized_view/materialized_view.sql +1200 -0
  21. dbt/include/doris/macros/materializations/seed/helpers.sql +68 -0
  22. dbt/include/doris/macros/materializations/snapshot/materialization.sql +212 -0
  23. dbt/include/doris/macros/materializations/snapshot/snapshot.sql +440 -0
  24. dbt/include/doris/macros/materializations/snapshot/strategies.sql +34 -0
  25. dbt/include/doris/macros/materializations/table/create_table_as.sql +266 -0
  26. dbt/include/doris/macros/materializations/table/table.sql +130 -0
  27. dbt/include/doris/macros/materializations/view/create_view_as.sql +65 -0
  28. dbt/include/doris/macros/materializations/view/view.sql +54 -0
  29. dbt/include/doris/profile_template.yml +42 -0
  30. dbt/include/doris/sample_profiles.yml +27 -0
  31. dbt_for_apache_doris-1.1.0.dist-info/METADATA +285 -0
  32. dbt_for_apache_doris-1.1.0.dist-info/RECORD +36 -0
  33. dbt_for_apache_doris-1.1.0.dist-info/WHEEL +5 -0
  34. dbt_for_apache_doris-1.1.0.dist-info/licenses/LICENSE +201 -0
  35. dbt_for_apache_doris-1.1.0.dist-info/licenses/NOTICE +14 -0
  36. dbt_for_apache_doris-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ from dbt.adapters.doris.connections import DorisConnectionManager # noqa
22
+ from dbt.adapters.doris.connections import DorisCredentials
23
+ from dbt.adapters.doris.impl import DorisAdapter
24
+
25
+ from dbt.adapters.base import AdapterPlugin
26
+ from dbt.include import doris
27
+
28
+
29
+ Plugin = AdapterPlugin(
30
+ adapter=DorisAdapter,
31
+ credentials=DorisCredentials,
32
+ include_path=doris.PACKAGE_PATH,
33
+ )
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+
22
+ # this 'version' must be set !!!
23
+ # otherwise the adapters will not be found after the 'dbt init xxx' command
24
+
25
+ version = "1.1.0"
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ from dataclasses import dataclass
22
+ import re
23
+
24
+ from dbt.adapters.base.column import Column
25
+
26
+
27
+ @dataclass
28
+ class DorisColumn(Column):
29
+ @classmethod
30
+ def from_description(cls, name: str, raw_data_type: str) -> "DorisColumn":
31
+ """Parse only Doris types whose parameters dbt must reason about.
32
+
33
+ The generic dbt parser truncates nested types such as
34
+ ``ARRAY<VARCHAR(20)>`` and parameterized non-numeric types such as
35
+ ``DATETIMEV2(6)``. Preserve those verbatim while structuring VARCHAR and
36
+ DECIMAL so schema comparison and safe string widening retain their
37
+ sizes.
38
+ """
39
+ raw_data_type = raw_data_type.strip()
40
+ varchar = re.fullmatch(
41
+ r"varchar\s*\(\s*(\d+)\s*\)",
42
+ raw_data_type,
43
+ flags=re.IGNORECASE,
44
+ )
45
+ if varchar is not None:
46
+ return cls(name, "varchar", char_size=int(varchar.group(1)))
47
+
48
+ decimal = re.fullmatch(
49
+ r"decimal\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)",
50
+ raw_data_type,
51
+ flags=re.IGNORECASE,
52
+ )
53
+ if decimal is not None:
54
+ return cls(
55
+ name,
56
+ "decimal",
57
+ numeric_precision=int(decimal.group(1)),
58
+ numeric_scale=int(decimal.group(2)),
59
+ )
60
+
61
+ return cls(name, raw_data_type)
62
+
63
+ @classmethod
64
+ def string_type(cls, size: int) -> str:
65
+ """Render Doris syntax rather than dbt's unsupported CHARACTER VARYING."""
66
+ return "varchar({})".format(size)
67
+
68
+ @property
69
+ def quoted(self) -> str:
70
+ return "`{}`".format(self.column)
71
+
72
+ def __repr__(self) -> str:
73
+ return f"<DorisColumn {self.name} ({self.data_type})>"
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ from contextlib import contextmanager
22
+ from dataclasses import dataclass
23
+ from typing import ContextManager, Optional, Union
24
+
25
+ import mysql.connector
26
+ from mysql.connector.constants import FieldType
27
+
28
+ from dbt import exceptions
29
+ from dbt.adapters.contracts.connection import Credentials
30
+ from dbt.adapters.sql import SQLConnectionManager
31
+ from dbt.adapters.contracts.connection import AdapterResponse, Connection
32
+ from dbt.adapters.events.logging import AdapterLogger
33
+
34
+ logger = AdapterLogger("doris")
35
+
36
+
37
+ @dataclass
38
+ class DorisCredentials(Credentials):
39
+ host: str = "127.0.0.1"
40
+ port: int = 9030
41
+ username: str = "root"
42
+ password: str = ""
43
+ database: Optional[str] = None
44
+ schema: Optional[str] = None
45
+
46
+ @property
47
+ def type(self):
48
+ return "doris"
49
+
50
+ def _connection_keys(self):
51
+ return "host", "port", "username", "schema"
52
+
53
+ @property
54
+ def unique_field(self) -> str:
55
+ return self.schema
56
+
57
+ def __post_init__(self):
58
+ if self.database is not None and self.database != self.schema:
59
+ raise exceptions.DbtRuntimeError(
60
+ f" schema: {self.schema} \n"
61
+ f" database: {self.database} \n"
62
+ f"On Doris, database must be omitted or have the same value as"
63
+ f" schema."
64
+ )
65
+
66
+
67
+ class DorisConnectionManager(SQLConnectionManager):
68
+ TYPE = "doris"
69
+
70
+ @classmethod
71
+ def open(cls, connection: Connection) -> Connection:
72
+ if connection.state == "open":
73
+ logger.debug("Connection is already open, skipping open")
74
+ return connection
75
+ credentials = cls.get_credentials(connection.credentials)
76
+ kwargs = {
77
+ "host": credentials.host,
78
+ "port": credentials.port,
79
+ "user": credentials.username,
80
+ "password": credentials.password,
81
+ "database": credentials.schema,
82
+ "buffered": True,
83
+ "charset": "utf8",
84
+ "get_warnings": True,
85
+ }
86
+
87
+ try:
88
+ connection.handle = mysql.connector.connect(**kwargs)
89
+ connection.state = 'open'
90
+ except mysql.connector.Error as e:
91
+ # If the database does not exist yet, connect without it.
92
+ # dbt will create the database/schema via create_schema().
93
+ if e.errno == 1049: # Unknown database
94
+ logger.debug(
95
+ f"Database '{credentials.schema}' does not exist, "
96
+ "connecting without database."
97
+ )
98
+ kwargs.pop("database", None)
99
+ try:
100
+ connection.handle = mysql.connector.connect(**kwargs)
101
+ connection.state = 'open'
102
+ except mysql.connector.Error as e2:
103
+ logger.debug(
104
+ "Got an error when attempting to open a Doris "
105
+ "connection: '{}'".format(e2)
106
+ )
107
+ connection.handle = None
108
+ connection.state = 'fail'
109
+ raise exceptions.DbtRuntimeError(str(e2))
110
+ else:
111
+ logger.debug("Got an error when attempting to open a Doris "
112
+ "connection: '{}'"
113
+ .format(e))
114
+
115
+ connection.handle = None
116
+ connection.state = 'fail'
117
+
118
+ raise exceptions.DbtRuntimeError(str(e))
119
+ return connection
120
+
121
+ @classmethod
122
+ def get_credentials(cls, credentials):
123
+ return credentials
124
+
125
+ def cancel(self, connection: Connection):
126
+ connection.handle.close()
127
+
128
+ @classmethod
129
+ def get_response(cls, cursor) -> Union[AdapterResponse, str]:
130
+ code = "SUCCESS"
131
+ num_rows = 0
132
+
133
+ if cursor is not None and cursor.rowcount is not None:
134
+ num_rows = cursor.rowcount
135
+ return AdapterResponse(
136
+ code=code,
137
+ _message=f"{num_rows} rows affected",
138
+ rows_affected=num_rows,
139
+ )
140
+
141
+ @contextmanager
142
+ def exception_handler(self, sql: str) -> ContextManager:
143
+ try:
144
+ yield
145
+ except mysql.connector.Error as e:
146
+ logger.debug(f"Doris database error: {e}, sql: {sql}")
147
+ raise exceptions.DbtRuntimeError(str(e)) from e
148
+ except Exception as e:
149
+ logger.debug(f"Error running SQL: {sql}")
150
+ if isinstance(e, exceptions.DbtRuntimeError):
151
+ raise e
152
+ raise exceptions.DbtRuntimeError(str(e)) from e
153
+
154
+ @classmethod
155
+ def data_type_code_to_name(cls, type_code) -> str:
156
+ """Map mysql-connector type codes to Doris type names."""
157
+ mapping = {
158
+ FieldType.TINY: "TINYINT",
159
+ FieldType.SHORT: "SMALLINT",
160
+ FieldType.LONG: "INT",
161
+ FieldType.FLOAT: "FLOAT",
162
+ FieldType.DOUBLE: "DOUBLE",
163
+ FieldType.NULL: "NULL",
164
+ FieldType.TIMESTAMP: "DATETIME",
165
+ FieldType.LONGLONG: "BIGINT",
166
+ FieldType.INT24: "INT",
167
+ FieldType.DATE: "DATE",
168
+ FieldType.TIME: "TIME",
169
+ FieldType.DATETIME: "DATETIME",
170
+ FieldType.YEAR: "INT",
171
+ FieldType.NEWDATE: "DATE",
172
+ FieldType.VARCHAR: "VARCHAR",
173
+ FieldType.BIT: "BOOLEAN",
174
+ FieldType.JSON: "JSON",
175
+ FieldType.NEWDECIMAL: "DECIMAL",
176
+ FieldType.DECIMAL: "DECIMAL",
177
+ FieldType.ENUM: "VARCHAR",
178
+ FieldType.SET: "VARCHAR",
179
+ FieldType.TINY_BLOB: "STRING",
180
+ FieldType.MEDIUM_BLOB: "STRING",
181
+ FieldType.LONG_BLOB: "STRING",
182
+ FieldType.BLOB: "STRING",
183
+ FieldType.VAR_STRING: "VARCHAR",
184
+ FieldType.STRING: "STRING",
185
+ FieldType.GEOMETRY: "STRING",
186
+ }
187
+ return mapping.get(type_code, "STRING")
188
+
189
+ def begin(self):
190
+ """
191
+ Doris BEGIN limitation: once BEGIN is issued, only INSERT/UPDATE/DELETE/
192
+ COMMIT/ROLLBACK are allowed — SELECT and DDL will error with:
193
+ "This is in a transaction, only insert, update, delete, commit, rollback
194
+ is acceptable."
195
+
196
+ We must NOT send literal BEGIN SQL. We only maintain dbt-core's
197
+ transaction_open flag so the framework tracks state correctly.
198
+ """
199
+ connection = self.get_thread_connection()
200
+ if connection.transaction_open is True:
201
+ raise exceptions.DbtRuntimeError(
202
+ "Tried to begin a new transaction on connection '{}', but "
203
+ "it already had one open!".format(connection.name)
204
+ )
205
+ connection.transaction_open = True
206
+ return connection
207
+
208
+ def commit(self):
209
+ """
210
+ Do not send literal COMMIT SQL — bare COMMIT without BEGIN is a no-op
211
+ in Doris, but we avoid it for clarity. Just reset the framework flag.
212
+ """
213
+ connection = self.get_thread_connection()
214
+ connection.transaction_open = False
215
+ return connection
216
+
217
+ def add_begin_query(self):
218
+ """Override to prevent literal 'BEGIN' SQL from being sent to Doris."""
219
+ pass
220
+
221
+ def add_commit_query(self):
222
+ """Override to prevent literal 'COMMIT' SQL from being sent to Doris."""
223
+ pass
224
+
225
+ def add_query(
226
+ self,
227
+ sql,
228
+ auto_begin=True,
229
+ bindings=None,
230
+ abridge_sql_log=False,
231
+ retryable_exceptions=(),
232
+ retry_limit=1,
233
+ ):
234
+ """Run a query, then drain any extra result sets it produced.
235
+
236
+ mysql-connector executes semicolon-separated statements in a single
237
+ `execute()` call but only surfaces the first result. The remaining sets
238
+ stay queued on the connection, and the next statement fails with
239
+ `2014 (HY000) Commands out of sync; you can't run this command now`.
240
+ The originating statement itself succeeds, so the error surfaces on
241
+ whatever dbt does next -- typically dropping a temp relation, which then
242
+ leaks.
243
+
244
+ Macros are expected to emit one statement per dbt statement, so this is a
245
+ safety net rather than the primary fix. Draining only runs for statements
246
+ that produced no fetchable rows, which leaves ordinary SELECT results
247
+ intact for dbt to consume.
248
+ """
249
+ connection, cursor = super().add_query(
250
+ sql,
251
+ auto_begin=auto_begin,
252
+ bindings=bindings,
253
+ abridge_sql_log=abridge_sql_log,
254
+ retryable_exceptions=retryable_exceptions,
255
+ retry_limit=retry_limit,
256
+ )
257
+
258
+ if cursor is not None and not cursor.with_rows:
259
+ drained = 0
260
+ with self.exception_handler(sql):
261
+ while cursor.nextset():
262
+ drained += 1
263
+ if drained:
264
+ logger.debug(
265
+ f"Drained {drained} extra result set(s); a macro emitted "
266
+ f"multiple statements in one dbt statement: {sql}"
267
+ )
268
+
269
+ return connection, cursor
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python
2
+ # encoding: utf-8
3
+
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ class DorisColumnItem:
22
+ def __init__(self, col_name, col_type, col_comment, col_default):
23
+ self._col_name = col_name
24
+ self._col_type = col_type
25
+ self._col_comment = col_comment
26
+ self._col_default = col_default
27
+
28
+ def get_col_name(self):
29
+ return self._col_name
30
+
31
+ def get_col_type(self):
32
+ return self._col_type
33
+
34
+ def get_col_comment(self):
35
+ return self._col_comment
36
+
37
+ def get_col_default(self):
38
+ return self._col_default
39
+
40
+ def get_view_column_constraint(self):
41
+ res = ""
42
+ if self._col_comment != "":
43
+ res = f"`{self._col_name}` COMMENT '{self._col_comment}'"
44
+ else:
45
+ res = f"`{self._col_name}`"
46
+ return res
47
+
48
+ def get_table_column_constraint(self):
49
+ res = ""
50
+ if self._col_type is not None:
51
+ res = f"cast(`{self._col_name}` as {self._col_type}) as `{self._col_name}`"
52
+ else:
53
+ res = f"`{self._col_name}`"
54
+ return res