SCUDO 0.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.
- cudo/__init__.py +0 -0
- cudo/doris/__init__.py +0 -0
- cudo/doris/client.py +310 -0
- cudo/doris/pymysql.py +380 -0
- cudo/minio/__init__.py +0 -0
- cudo/minio/minio.py +300 -0
- cudo/spark/__init__.py +0 -0
- cudo/spark/spark.py +239 -0
- cudo/utils/__init__.py +0 -0
- cudo/utils/clinic.py +109 -0
- cudo/utils/logging.py +96 -0
- cudo/utils/vcf.py +319 -0
- scudo-0.1.0.dist-info/METADATA +9 -0
- scudo-0.1.0.dist-info/RECORD +16 -0
- scudo-0.1.0.dist-info/WHEEL +5 -0
- scudo-0.1.0.dist-info/top_level.txt +1 -0
cudo/__init__.py
ADDED
|
File without changes
|
cudo/doris/__init__.py
ADDED
|
File without changes
|
cudo/doris/client.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
# Doris Flight SQL Client (ADBC)
|
|
6
|
+
|
|
7
|
+
Python client for interacting with Apache Doris using Arrow Flight SQL via ADBC.
|
|
8
|
+
|
|
9
|
+
This class provides:
|
|
10
|
+
- Simple connection handling
|
|
11
|
+
- Query execution
|
|
12
|
+
- Optimized DataFrame fetching (`fetch_df`)
|
|
13
|
+
- Optional Arrow-based and partition-based methods
|
|
14
|
+
- Timing and logging utilities
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install adbc_driver_manager adbc_driver_flightsql pandas
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Example
|
|
23
|
+
client = DorisFlightSQLClient(
|
|
24
|
+
uri="grpc://127.0.0.1:9090",
|
|
25
|
+
username="root",
|
|
26
|
+
password=""
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
df = client.query("SELECT * FROM my_table LIMIT 1000;")
|
|
30
|
+
print(df.head())
|
|
31
|
+
|
|
32
|
+
client.close()
|
|
33
|
+
'''
|
|
34
|
+
|
|
35
|
+
import adbc_driver_manager
|
|
36
|
+
import adbc_driver_flightsql.dbapi as flight_sql
|
|
37
|
+
import pandas as pd
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
from typing import Optional
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DorisFlightSQLClient:
|
|
44
|
+
'''
|
|
45
|
+
Apache Doris Flight SQL client using ADBC (DB-API 2.0).
|
|
46
|
+
This client wraps the ADBC Flight SQL driver and provides optimized
|
|
47
|
+
methods for querying data into pandas DataFrames.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
uri : str
|
|
52
|
+
Flight SQL endpoint (e.g., "grpc://127.0.0.1:9090").
|
|
53
|
+
|
|
54
|
+
username : str
|
|
55
|
+
Database username.
|
|
56
|
+
|
|
57
|
+
password : str
|
|
58
|
+
Database password.
|
|
59
|
+
|
|
60
|
+
Attributes
|
|
61
|
+
----------
|
|
62
|
+
conn : flight_sql.Connection
|
|
63
|
+
Active database connection.
|
|
64
|
+
|
|
65
|
+
cursor : flight_sql.Cursor
|
|
66
|
+
Cursor used for executing queries.
|
|
67
|
+
|
|
68
|
+
Notes
|
|
69
|
+
-----
|
|
70
|
+
- Uses `fetch_df()` by default for best performance.
|
|
71
|
+
- Compatible with Apache Doris Arrow Flight SQL service.
|
|
72
|
+
'''
|
|
73
|
+
|
|
74
|
+
def __init__(self, uri: str, username: str, password: str) -> None:
|
|
75
|
+
self.uri = uri
|
|
76
|
+
self.db_kwargs = {
|
|
77
|
+
adbc_driver_manager.DatabaseOptions.USERNAME.value: username,
|
|
78
|
+
adbc_driver_manager.DatabaseOptions.PASSWORD.value: password,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
self.conn: Optional[flight_sql.Connection] = None
|
|
82
|
+
self.cursor: Optional[flight_sql.Cursor] = None
|
|
83
|
+
|
|
84
|
+
self._connect()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
# Connection Handling
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def _connect(self) -> None:
|
|
92
|
+
'''
|
|
93
|
+
Establish a connection and create a cursor.
|
|
94
|
+
'''
|
|
95
|
+
try:
|
|
96
|
+
self.conn = flight_sql.connect(uri=self.uri, db_kwargs=self.db_kwargs)
|
|
97
|
+
self.cursor = self.conn.cursor()
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise RuntimeError("Connection failed") from e
|
|
100
|
+
|
|
101
|
+
def close(self) -> None:
|
|
102
|
+
'''
|
|
103
|
+
Close cursor and connection.
|
|
104
|
+
'''
|
|
105
|
+
try:
|
|
106
|
+
if self.cursor:
|
|
107
|
+
self.cursor.close()
|
|
108
|
+
finally:
|
|
109
|
+
if self.conn:
|
|
110
|
+
self.conn.close()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ------------------------------------------------------------------
|
|
114
|
+
# Core Query Method
|
|
115
|
+
# ------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
def query(self, sql: str, params: dict | None = None) -> pd.DataFrame:
|
|
118
|
+
'''
|
|
119
|
+
Execute a SQL query and return a pandas DataFrame.
|
|
120
|
+
|
|
121
|
+
This method uses `fetch_df()` (ADBC optimized path),
|
|
122
|
+
which is faster than converting from Arrow manually.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
sql : str
|
|
127
|
+
SQL query string.
|
|
128
|
+
|
|
129
|
+
params : dict, optional
|
|
130
|
+
Parameters to safely inject into SQL.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
pandas.DataFrame
|
|
135
|
+
Query result as a DataFrame.
|
|
136
|
+
|
|
137
|
+
Example
|
|
138
|
+
-------
|
|
139
|
+
>>> df = client.query("SELECT * FROM table LIMIT 100;")
|
|
140
|
+
|
|
141
|
+
'''
|
|
142
|
+
if not self.cursor:
|
|
143
|
+
raise RuntimeError("Cursor is not initialized.")
|
|
144
|
+
|
|
145
|
+
sql = self._render_sql(sql, params=params)
|
|
146
|
+
|
|
147
|
+
start_time = datetime.now()
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
self.cursor.execute(sql)
|
|
151
|
+
df = self.cursor.fetch_df()
|
|
152
|
+
except Exception as e:
|
|
153
|
+
raise RuntimeError("Query execution failed") from e
|
|
154
|
+
|
|
155
|
+
duration = datetime.now() - start_time
|
|
156
|
+
|
|
157
|
+
print("\n##################")
|
|
158
|
+
print(f"Query executed in: {duration}")
|
|
159
|
+
print(df.info(memory_usage="deep"))
|
|
160
|
+
|
|
161
|
+
return df
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ------------------------------------------------------------------
|
|
165
|
+
# Alternative Methods
|
|
166
|
+
# ------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
def query_arrow(self, sql: str, params: dict | None = None) -> pd.DataFrame:
|
|
169
|
+
'''
|
|
170
|
+
Execute query using Arrow fetch, then convert to pandas.
|
|
171
|
+
|
|
172
|
+
Slower than `query()` but useful for debugging or Arrow workflows.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
sql : str
|
|
177
|
+
SQL query.
|
|
178
|
+
|
|
179
|
+
params : dict, optional
|
|
180
|
+
Parameters to safely inject into SQL.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
pandas.DataFrame
|
|
185
|
+
'''
|
|
186
|
+
sql = self._render_sql(sql, params=params)
|
|
187
|
+
|
|
188
|
+
start_time = datetime.now()
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
self.cursor.execute(sql)
|
|
192
|
+
arrow_data = self.cursor.fetchallarrow()
|
|
193
|
+
df = arrow_data.to_pandas()
|
|
194
|
+
except Exception as e:
|
|
195
|
+
raise RuntimeError("Arrow query execution failed") from e
|
|
196
|
+
|
|
197
|
+
duration = datetime.now() - start_time
|
|
198
|
+
|
|
199
|
+
print("\n##################")
|
|
200
|
+
print(f"Arrow fetch in: {duration}, bytes: {arrow_data.nbytes}")
|
|
201
|
+
print(df.info(memory_usage="deep"))
|
|
202
|
+
|
|
203
|
+
return df
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def query_partition(self, sql: str, params: dict | None = None) -> pd.DataFrame:
|
|
207
|
+
'''
|
|
208
|
+
Execute query using partitioned reads.
|
|
209
|
+
|
|
210
|
+
Useful for parallel processing of large datasets.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
sql : str
|
|
215
|
+
SQL query.
|
|
216
|
+
|
|
217
|
+
params : dict, optional
|
|
218
|
+
Parameters to safely inject into SQL.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
pandas.DataFrame
|
|
223
|
+
'''
|
|
224
|
+
sql = self._render_sql(sql, params=params)
|
|
225
|
+
|
|
226
|
+
start_time = datetime.now()
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
partitions, schema = self.cursor.adbc_execute_partitions(sql)
|
|
230
|
+
# Example: read first partition (extendable to parallel processing)
|
|
231
|
+
self.cursor.adbc_read_partition(partitions[0])
|
|
232
|
+
arrow_data = self.cursor.fetchallarrow()
|
|
233
|
+
df = arrow_data.to_pandas()
|
|
234
|
+
except Exception as e:
|
|
235
|
+
raise RuntimeError("Partition query execution failed") from e
|
|
236
|
+
|
|
237
|
+
duration = datetime.now() - start_time
|
|
238
|
+
|
|
239
|
+
print("\n##################")
|
|
240
|
+
print(f"Partitions: {len(partitions)}, time: {duration}")
|
|
241
|
+
print(df.info(memory_usage="deep"))
|
|
242
|
+
|
|
243
|
+
return df
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ------------------------------------------------------------------
|
|
247
|
+
# Utility Methods
|
|
248
|
+
# ------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def _render_sql(self, sql: str, params: dict | None = None) -> str:
|
|
251
|
+
'''
|
|
252
|
+
Render SQL using simple parameter substitution.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
sql : str
|
|
257
|
+
SQL query with `{param}` placeholders.
|
|
258
|
+
|
|
259
|
+
params : dict, optional
|
|
260
|
+
Dictionary of parameters.
|
|
261
|
+
|
|
262
|
+
Returns
|
|
263
|
+
-------
|
|
264
|
+
str
|
|
265
|
+
Rendered SQL string.
|
|
266
|
+
'''
|
|
267
|
+
if params:
|
|
268
|
+
safe_params = {}
|
|
269
|
+
for k, v in params.items():
|
|
270
|
+
if isinstance(v, (int, float)):
|
|
271
|
+
safe_params[k] = v
|
|
272
|
+
elif isinstance(v, str):
|
|
273
|
+
safe_params[k] = "'" + v.replace("'", "''") + "'"
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(f"Unsafe parameter type for {k}: {type(v)}")
|
|
276
|
+
try:
|
|
277
|
+
return sql.format(**safe_params)
|
|
278
|
+
except KeyError as e:
|
|
279
|
+
raise ValueError(f"Missing SQL parameter: {e}") from e
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def execute(self, sql: str) -> None:
|
|
283
|
+
'''
|
|
284
|
+
Execute a SQL statement without returning results.
|
|
285
|
+
|
|
286
|
+
Useful for DDL or INSERT operations.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
sql : str
|
|
291
|
+
'''
|
|
292
|
+
if not self.cursor:
|
|
293
|
+
raise RuntimeError("Cursor is not initialized.")
|
|
294
|
+
|
|
295
|
+
print(f"\n### Executing SQL ###\n{sql}")
|
|
296
|
+
self.cursor.execute(sql)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def __enter__(self):
|
|
300
|
+
''' Context manager entry.'''
|
|
301
|
+
return self
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
305
|
+
''' Context manager exit (auto-close).'''
|
|
306
|
+
if exc_type:
|
|
307
|
+
print(f"[DorisFlightSQLClient] Exception: {exc_val}")
|
|
308
|
+
self.close()
|
|
309
|
+
return False # always propagate errors
|
|
310
|
+
|
cudo/doris/pymysql.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Doris Client Module
|
|
3
|
+
==============================
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
This module provides a lightweight client for interacting with **Apache Doris**
|
|
7
|
+
using a MySQL-compatible interface.
|
|
8
|
+
|
|
9
|
+
It encapsulates common operations required for data loading workflows:
|
|
10
|
+
- Establishing a connection to Doris
|
|
11
|
+
- Submitting LOAD jobs
|
|
12
|
+
- Monitoring job execution status
|
|
13
|
+
- Waiting for job completion
|
|
14
|
+
|
|
15
|
+
The implementation is designed to be simple, reusable, and easily integrable
|
|
16
|
+
into ETL/ELT pipelines.
|
|
17
|
+
|
|
18
|
+
Main Features
|
|
19
|
+
-------------
|
|
20
|
+
|
|
21
|
+
- Connection management using `pymysql`
|
|
22
|
+
- Load job submission via SQL
|
|
23
|
+
- Polling-based job monitoring
|
|
24
|
+
- Optional logging integration
|
|
25
|
+
|
|
26
|
+
Example Usage
|
|
27
|
+
-------------
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
client = DorisClient(config, logger)
|
|
31
|
+
|
|
32
|
+
client.submit_load("my_db", load_sql)
|
|
33
|
+
client.wait_for_load("my_label")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
'''
|
|
37
|
+
|
|
38
|
+
import time
|
|
39
|
+
import pymysql
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DorisPyMySQLClient:
|
|
43
|
+
'''
|
|
44
|
+
Client for interacting with Apache Doris.
|
|
45
|
+
|
|
46
|
+
This class provides methods to execute and monitor data load operations
|
|
47
|
+
in Apache Doris using its MySQL-compatible interface.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
config (dict):
|
|
51
|
+
Configuration dictionary containing connection parameters:
|
|
52
|
+
- host (str): Doris FE host
|
|
53
|
+
- port (int): Doris FE MySQL port
|
|
54
|
+
- user (str): Username
|
|
55
|
+
- password (str): Password
|
|
56
|
+
- autocommit (bool): Autocommit flag
|
|
57
|
+
|
|
58
|
+
logger (logging.Logger, optional):
|
|
59
|
+
Logger instance for logging execution details.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
conn (pymysql.Connection):
|
|
63
|
+
Active database connection.
|
|
64
|
+
'''
|
|
65
|
+
def __init__(self, config, logger=None):
|
|
66
|
+
self.logger = logger
|
|
67
|
+
self.conn = self._connect(config)
|
|
68
|
+
|
|
69
|
+
def _connect(self, config):
|
|
70
|
+
'''
|
|
71
|
+
Establish a connection to Apache Doris.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
config (dict):
|
|
75
|
+
Dictionary containing connection parameters.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
pymysql.Connection:
|
|
79
|
+
Active connection to Doris.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
RuntimeError:
|
|
83
|
+
If the connection cannot be established.
|
|
84
|
+
'''
|
|
85
|
+
try:
|
|
86
|
+
conn = pymysql.connect(
|
|
87
|
+
host=config["host"],
|
|
88
|
+
port=config["port"],
|
|
89
|
+
user=config["user"],
|
|
90
|
+
password=config["password"],
|
|
91
|
+
autocommit=config["autocommit"]
|
|
92
|
+
)
|
|
93
|
+
return conn
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
if self.logger:
|
|
97
|
+
self.logger.error("Connection failed: %s", str(e))
|
|
98
|
+
raise RuntimeError("Module failed")
|
|
99
|
+
|
|
100
|
+
def close(self):
|
|
101
|
+
'''
|
|
102
|
+
Close Doris connection.
|
|
103
|
+
'''
|
|
104
|
+
if self.conn:
|
|
105
|
+
self.conn.close()
|
|
106
|
+
|
|
107
|
+
def submit_load(self, db_name, sql) -> None:
|
|
108
|
+
'''
|
|
109
|
+
Submit a LOAD job to Apache Doris.
|
|
110
|
+
|
|
111
|
+
This method switches to the target database and executes the provided
|
|
112
|
+
LOAD SQL statement.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
db_name (str):
|
|
116
|
+
Target database name.
|
|
117
|
+
|
|
118
|
+
sql (str):
|
|
119
|
+
Full Doris LOAD SQL statement.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
None
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
RuntimeError:
|
|
126
|
+
If the SQL execution fails.
|
|
127
|
+
'''
|
|
128
|
+
try:
|
|
129
|
+
with self.conn.cursor() as cur:
|
|
130
|
+
cur.execute(f"USE {db_name}")
|
|
131
|
+
cur.execute(sql)
|
|
132
|
+
|
|
133
|
+
if self.logger:
|
|
134
|
+
self.logger.info("Load submitted successfully")
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
if self.logger:
|
|
138
|
+
self.logger.error("Submit load failed: %s", str(e))
|
|
139
|
+
raise RuntimeError("Module failed")
|
|
140
|
+
|
|
141
|
+
def check_load_status(self, label) -> str:
|
|
142
|
+
'''
|
|
143
|
+
Check the status of a Doris LOAD job.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
label (str):
|
|
147
|
+
Unique label identifying the load job.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
str | None:
|
|
151
|
+
- Job state (e.g., ``FINISHED``, ``CANCELLED``)
|
|
152
|
+
- ``None`` if the job is not yet visible
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
RuntimeError:
|
|
156
|
+
If the query execution fails.
|
|
157
|
+
'''
|
|
158
|
+
try:
|
|
159
|
+
with self.conn.cursor() as cur:
|
|
160
|
+
cur.execute(f"SHOW LOAD WHERE Label = '{label}'")
|
|
161
|
+
result = cur.fetchone()
|
|
162
|
+
|
|
163
|
+
if not result:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
return result[2] # state column
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
if self.logger:
|
|
170
|
+
self.logger.error("Check load status failed: %s", str(e))
|
|
171
|
+
raise RuntimeError("Module failed")
|
|
172
|
+
|
|
173
|
+
def wait_for_load(self, label) -> None:
|
|
174
|
+
'''
|
|
175
|
+
Wait until a Doris LOAD job completes.
|
|
176
|
+
|
|
177
|
+
This method continuously polls the job status until:
|
|
178
|
+
- The job finishes successfully (``FINISHED``)
|
|
179
|
+
- The job fails (``CANCELLED``)
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
label (str):
|
|
183
|
+
Load job label.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
None
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
RuntimeError:
|
|
190
|
+
If the load job fails or is cancelled.
|
|
191
|
+
'''
|
|
192
|
+
try:
|
|
193
|
+
while True:
|
|
194
|
+
state = self.check_load_status(label)
|
|
195
|
+
|
|
196
|
+
if state is None:
|
|
197
|
+
if self.logger:
|
|
198
|
+
self.logger.info("Load job not visible yet...")
|
|
199
|
+
|
|
200
|
+
elif state == "FINISHED":
|
|
201
|
+
if self.logger:
|
|
202
|
+
self.logger.info("Load job finished successfully")
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
elif state == "CANCELLED":
|
|
206
|
+
if self.logger:
|
|
207
|
+
self.logger.error("Doris load job failed")
|
|
208
|
+
raise RuntimeError("Doris load job failed")
|
|
209
|
+
|
|
210
|
+
else:
|
|
211
|
+
if self.logger:
|
|
212
|
+
self.logger.info(f"Current state: {state}")
|
|
213
|
+
|
|
214
|
+
time.sleep(5)
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
if self.logger:
|
|
218
|
+
self.logger.error("Wait for load failed: %s", str(e))
|
|
219
|
+
raise RuntimeError("Module failed")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# import time
|
|
224
|
+
# import pymysql
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# def connect_database(config, logger=None):
|
|
228
|
+
# '''
|
|
229
|
+
# Create a database connection to Doris
|
|
230
|
+
# '''
|
|
231
|
+
|
|
232
|
+
# try:
|
|
233
|
+
# conn = pymysql.connect(
|
|
234
|
+
# host=config["host"],
|
|
235
|
+
# port=config["port"],
|
|
236
|
+
# user=config["user"],
|
|
237
|
+
# password=config["password"],
|
|
238
|
+
# autocommit=config["autocommit"]
|
|
239
|
+
# )
|
|
240
|
+
# return conn
|
|
241
|
+
|
|
242
|
+
# except Exception as e:
|
|
243
|
+
# if logger: logger.error("module failed: %s", str(e))
|
|
244
|
+
# raise RuntimeError("Module failed")
|
|
245
|
+
|
|
246
|
+
# # to call:
|
|
247
|
+
# # wait_for_parquet(client,args.bucket, args.parquet_prefix )
|
|
248
|
+
# #
|
|
249
|
+
# # def wait_for_parquet(client, bucket, prefix, timeout=600):
|
|
250
|
+
|
|
251
|
+
# # start = time.time()
|
|
252
|
+
|
|
253
|
+
# # while True:
|
|
254
|
+
|
|
255
|
+
# # objects = list(client.list_objects(bucket, prefix=prefix, recursive=True))
|
|
256
|
+
# # parquet_files = [o.object_name for o in objects if o.object_name.endswith(".parquet")]
|
|
257
|
+
|
|
258
|
+
# # if parquet_files:
|
|
259
|
+
# # print(f"Found {len(parquet_files)} parquet files")
|
|
260
|
+
# # return parquet_files
|
|
261
|
+
|
|
262
|
+
# # if time.time() - start > timeout:
|
|
263
|
+
# # raise RuntimeError("Timeout waiting for parquet files")
|
|
264
|
+
|
|
265
|
+
# # print("No parquet files yet, sleeping 10s...")
|
|
266
|
+
# # time.sleep(10)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# def submit_load(conn, db_name, sql, logger=None):
|
|
270
|
+
# '''
|
|
271
|
+
# Submit a LOAD job to Apache Doris.
|
|
272
|
+
|
|
273
|
+
# Args:
|
|
274
|
+
# conn (pymysql.Connection):
|
|
275
|
+
# Active MySQL-compatible connection to Doris.
|
|
276
|
+
|
|
277
|
+
# db_name (str):
|
|
278
|
+
# Target database name.
|
|
279
|
+
|
|
280
|
+
# sql (str):
|
|
281
|
+
# Complete Doris LOAD SQL statement.
|
|
282
|
+
|
|
283
|
+
# Returns:
|
|
284
|
+
# None
|
|
285
|
+
|
|
286
|
+
# Raises:
|
|
287
|
+
# pymysql.MySQLError:
|
|
288
|
+
# If the SQL execution fails.
|
|
289
|
+
# '''
|
|
290
|
+
|
|
291
|
+
# try:
|
|
292
|
+
# with conn.cursor() as cur:
|
|
293
|
+
# cur.execute(f"USE {db_name}")
|
|
294
|
+
# cur.execute(sql)
|
|
295
|
+
|
|
296
|
+
# except Exception as e:
|
|
297
|
+
# if logger: logger.error("module failed: %s", str(e))
|
|
298
|
+
# raise RuntimeError("Module failed")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# def check_load_status(conn, label, logger=None):
|
|
302
|
+
# '''
|
|
303
|
+
# Retrieve the current state of a Doris LOAD job.
|
|
304
|
+
|
|
305
|
+
# Args:
|
|
306
|
+
# conn (pymysql.Connection):
|
|
307
|
+
# Active MySQL-compatible connection to Doris.
|
|
308
|
+
|
|
309
|
+
# label (str):
|
|
310
|
+
# Load job label used to identify the job.
|
|
311
|
+
|
|
312
|
+
# Returns:
|
|
313
|
+
# str | None:
|
|
314
|
+
# Current state of the job (e.g. ``FINISHED`` or ``CANCELLED``),
|
|
315
|
+
# or ``None`` if the job is not yet visible.
|
|
316
|
+
# '''
|
|
317
|
+
|
|
318
|
+
# try:
|
|
319
|
+
# with conn.cursor() as cur:
|
|
320
|
+
# cur.execute(f"SHOW LOAD WHERE Label = '{label}'")
|
|
321
|
+
# result = cur.fetchone()
|
|
322
|
+
|
|
323
|
+
# if not result:
|
|
324
|
+
# return None
|
|
325
|
+
|
|
326
|
+
# return result[2] # state column
|
|
327
|
+
|
|
328
|
+
# except Exception as e:
|
|
329
|
+
# if logger: logger.error("module failed: %s", str(e))
|
|
330
|
+
# raise RuntimeError("Module failed")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# def wait_for_load(conn, label, logger=None):
|
|
335
|
+
# '''
|
|
336
|
+
# Wait until a Doris LOAD job finishes.
|
|
337
|
+
|
|
338
|
+
# This function repeatedly polls the job status until the load
|
|
339
|
+
# finishes successfully or fails.
|
|
340
|
+
|
|
341
|
+
# Args:
|
|
342
|
+
# conn (pymysql.Connection):
|
|
343
|
+
# Active MySQL-compatible connection to Doris.
|
|
344
|
+
|
|
345
|
+
# label (str):
|
|
346
|
+
# Label identifying the load job.
|
|
347
|
+
|
|
348
|
+
# logger (logging.Logger, optional):
|
|
349
|
+
# Logger used to report progress.
|
|
350
|
+
|
|
351
|
+
# Returns:
|
|
352
|
+
# None
|
|
353
|
+
|
|
354
|
+
# Raises:
|
|
355
|
+
# RuntimeError:
|
|
356
|
+
# If the load job is cancelled or fails.
|
|
357
|
+
# '''
|
|
358
|
+
|
|
359
|
+
# try:
|
|
360
|
+
|
|
361
|
+
# while True:
|
|
362
|
+
|
|
363
|
+
# state = check_load_status(conn, label)
|
|
364
|
+
|
|
365
|
+
# if state is None:
|
|
366
|
+
# if logger: logger.info("load job not visible yet...")
|
|
367
|
+
# elif state == "FINISHED":
|
|
368
|
+
# if logger: logger.info("load job finished successfully")
|
|
369
|
+
# return
|
|
370
|
+
# elif state == "CANCELLED":
|
|
371
|
+
# if logger: logger.error("Doris load job failed")
|
|
372
|
+
# raise RuntimeError("Doris load job failed")
|
|
373
|
+
# else:
|
|
374
|
+
# if logger: logger.info(f"Current state: {state}")
|
|
375
|
+
|
|
376
|
+
# time.sleep(5)
|
|
377
|
+
|
|
378
|
+
# except Exception as e:
|
|
379
|
+
# if logger: logger.error("module failed: %s", str(e))
|
|
380
|
+
# raise RuntimeError("Module failed")
|
cudo/minio/__init__.py
ADDED
|
File without changes
|