lesscode-flask 0.2.91__tar.gz → 0.2.98__tar.gz

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 (75) hide show
  1. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/PKG-INFO +3 -3
  2. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/__init__.py +1 -1
  3. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/setup/__init__.py +1 -1
  4. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/sign/signature.py +39 -8
  5. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask.egg-info/PKG-INFO +3 -3
  6. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask.egg-info/SOURCES.txt +1 -0
  7. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask.egg-info/requires.txt +2 -2
  8. lesscode_flask-0.2.98/redash/query_runner/dameng.py +218 -0
  9. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/README.md +0 -0
  10. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/app.py +0 -0
  11. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/db/__init__.py +0 -0
  12. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/db/datasource.py +0 -0
  13. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/db/executor.py +0 -0
  14. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/export_data/__init__.py +0 -0
  15. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/export_data/data_download_handler.py +0 -0
  16. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/log/access_log_handler.py +0 -0
  17. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/access_log.py +0 -0
  18. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/base_model.py +0 -0
  19. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/parameterized_query.py +0 -0
  20. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/resource_param_template.py +0 -0
  21. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/response_result.py +0 -0
  22. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/user.py +0 -0
  23. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/model/user_limit_policy.py +0 -0
  24. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/service/access_log_service.py +0 -0
  25. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/service/base_service.py +0 -0
  26. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/service/resource_param_template_service.py +0 -0
  27. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/setting/__init__.py +0 -0
  28. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/signals.py +0 -0
  29. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/static/swagger.py +0 -0
  30. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/__init__.py +0 -0
  31. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/decorator/__init__.py +0 -0
  32. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/decorator/cache.py +0 -0
  33. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/decorator/sql_injection.py +0 -0
  34. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/decorator/swagger.py +0 -0
  35. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/dify_utils.py +0 -0
  36. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/file/file_exporter.py +0 -0
  37. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/file/file_utils.py +0 -0
  38. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/fs_util.py +0 -0
  39. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/helpers.py +0 -0
  40. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/json/NotSortJSONProvider.py +0 -0
  41. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/__init__.py +0 -0
  42. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/consecutive/consecutive_limiter_handler.py +0 -0
  43. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/consecutive/redis_consecutive_limiter.py +0 -0
  44. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/limit_util.py +0 -0
  45. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/req/rate_limiter_handler.py +0 -0
  46. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/req/redis_rate_limiter.py +0 -0
  47. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/req_count/count_limiter_handler.py +0 -0
  48. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/limit/req_count/redis_count_limiter.py +0 -0
  49. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/oss/__init__.py +0 -0
  50. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/oss/aliyun_oss.py +0 -0
  51. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/oss/ks3_oss.py +0 -0
  52. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/oss/minio_oss.py +0 -0
  53. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/redis/redis_helper.py +0 -0
  54. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/request/request.py +0 -0
  55. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/sign/__init__.py +0 -0
  56. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/swagger/swagger_template.py +0 -0
  57. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/swagger/swagger_util.py +0 -0
  58. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/task/__init__.py +0 -0
  59. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/task/task_helper.py +0 -0
  60. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/utils/thread/thread_utils.py +0 -0
  61. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask/wsgi.py +0 -0
  62. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask.egg-info/dependency_links.txt +0 -0
  63. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/lesscode_flask.egg-info/top_level.txt +0 -0
  64. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/query_runner/__init__.py +0 -0
  65. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/query_runner/clickhouse.py +0 -0
  66. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/query_runner/elasticsearch.py +0 -0
  67. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/query_runner/kingbase.py +0 -0
  68. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/query_runner/mysql.py +0 -0
  69. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/query_runner/pg.py +0 -0
  70. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/settings/__init__.py +0 -0
  71. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/settings/helpers.py +0 -0
  72. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/utils/__init__.py +0 -0
  73. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/redash/utils/requests_session.py +0 -0
  74. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/setup.cfg +0 -0
  75. {lesscode_flask-0.2.91 → lesscode_flask-0.2.98}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lesscode-flask
3
- Version: 0.2.91
3
+ Version: 0.2.98
4
4
  Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
5
5
  Home-page: https://lesscode-flask
6
6
  Author: Chao.yy
@@ -11,7 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: Flask==3.0.3
14
- Requires-Dist: lesscode-utils==0.0.94
14
+ Requires-Dist: lesscode-utils==0.0.95
15
15
  Requires-Dist: Flask-Login==0.6.3
16
16
  Requires-Dist: redis==5.1.1
17
17
  Requires-Dist: flask-swagger-ui==4.11.1
@@ -24,7 +24,7 @@ Requires-Dist: requests==2.32.3
24
24
  Requires-Dist: gunicorn==23.0.0
25
25
  Requires-Dist: gevent==25.5.1
26
26
  Requires-Dist: openpyxl==3.1.5
27
- Requires-Dist: iputil==0.3.1
27
+ Requires-Dist: iputil==0.3.2
28
28
  Requires-Dist: phone==0.4.5
29
29
  Requires-Dist: psutil==7.2.2
30
30
  Dynamic: author
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.91"
1
+ __version__ = "0.2.98"
2
2
 
3
3
  import functools
4
4
  import logging
@@ -189,7 +189,7 @@ def _build_download_key(target_url, request_method):
189
189
  key_seed = f"{str(request_method).upper()}::{target_url}"
190
190
  # 保持原有 AES 风格,同时拼接首尾片段,避免只截前缀导致不同接口冲突。
191
191
  encrypted = AES.encrypt(key_seed)
192
- return f"{encrypted[:8]}{encrypted[-8:]}"
192
+ return f"{encrypted[:16]}{encrypted[-16:]}"
193
193
 
194
194
 
195
195
  def _is_auto_export_route(route):
@@ -1,6 +1,7 @@
1
1
  import hashlib
2
2
  import hmac
3
3
  import ipaddress
4
+ import logging
4
5
  import time
5
6
 
6
7
  from flask import request
@@ -9,11 +10,38 @@ from lesscode_flask.model.response_result import ResponseResult
9
10
  from lesscode_flask.utils.redis.redis_helper import RedisHelper
10
11
 
11
12
  SIGN_ERROR_MESSAGE = "未知错误"
13
+ SIGN_ERROR_CODE_MAP = {
14
+ "missing_sign_headers": "01",
15
+ "missing_sign_secret": "02",
16
+ "invalid_timestamp": "03",
17
+ "timestamp_out_of_window": "04",
18
+ "nonce_replayed": "05",
19
+ "nonce_check_exception": "06",
20
+ "signature_mismatch": "07",
21
+ }
22
+
23
+
24
+ def _sign_error_message(error_key: str) -> str:
25
+ error_code = SIGN_ERROR_CODE_MAP.get(error_key)
26
+ if not error_code:
27
+ return SIGN_ERROR_MESSAGE
28
+ return f"{SIGN_ERROR_MESSAGE}{error_code}"
29
+
30
+
31
+ def _fail_sign(error_key: str):
32
+ ResponseResult.fail(
33
+ message=_sign_error_message(error_key),
34
+ status_code="401",
35
+ http_code="401",
36
+ )
12
37
 
13
38
 
14
39
  def _body_sha256() -> str:
15
40
  # 对原始请求体做哈希,保证不同 content-type 下签名输入一致。
16
41
  body_bytes = request.get_data(cache=True) or b""
42
+ logging.info("body_bytes开始")
43
+ logging.info("body_bytes: %s", body_bytes)
44
+ logging.info("body_bytes结束")
17
45
  return hashlib.sha256(body_bytes).hexdigest()
18
46
 
19
47
 
@@ -114,25 +142,25 @@ def verify_request_signature(app):
114
142
  signature = request.headers.get("Signature", "")
115
143
  sign_secret = app.config.get("SIGN_SECRET", "")
116
144
 
117
- # 任何签名基础参数缺失都统一返回“未知错误”,避免向外暴露细节。
145
+ # 同类错误固定使用同一个编号,方便客户端和服务端对齐排查。
118
146
  if not timestamp or not nonce or not signature:
119
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
147
+ _fail_sign("missing_sign_headers")
120
148
  # 服务端密钥未配置时也拦截,防止“空密钥”误放行。
121
149
  if not sign_secret:
122
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
150
+ _fail_sign("missing_sign_secret")
123
151
 
124
152
  # 时间戳必须是整数秒。
125
153
  try:
126
154
  ts = int(timestamp)
127
155
  except (TypeError, ValueError):
128
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
156
+ _fail_sign("invalid_timestamp")
129
157
  return
130
158
 
131
159
  # 时间窗校验,防止旧请求被重放。
132
160
  sign_window_sec = int(app.config.get("SIGN_WINDOW_SEC", 300))
133
161
  now_ts = int(time.time())
134
162
  if abs(now_ts - ts) > sign_window_sec:
135
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
163
+ _fail_sign("timestamp_out_of_window")
136
164
 
137
165
  if app.config.get("SIGN_NONCE_ENABLE", True):
138
166
  # 防重放:同一个 nonce 在 TTL 窗口内只能使用一次。
@@ -143,13 +171,16 @@ def verify_request_signature(app):
143
171
  nonce_key, "1", ex=nonce_ttl, nx=True
144
172
  )
145
173
  if not ok:
146
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
174
+ _fail_sign("nonce_replayed")
147
175
  except Exception:
148
176
  # Redis 异常按失败处理,避免在防重放失效时放过请求。
149
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
177
+ _fail_sign("nonce_check_exception")
150
178
 
151
179
  # 按统一规则组装签名串并计算服务端签名。
152
180
  sign_content = _canonical_sign_content(timestamp=timestamp, nonce=nonce)
181
+ logging.info("sign_content开始")
182
+ logging.info("sign_content: %s", sign_content)
183
+ logging.info("sign_content结束")
153
184
  expected = hmac.new(
154
185
  sign_secret.encode("utf-8"),
155
186
  sign_content.encode("utf-8"),
@@ -157,4 +188,4 @@ def verify_request_signature(app):
157
188
  ).hexdigest()
158
189
  # 常量时间比较,降低时序侧信道风险。
159
190
  if not hmac.compare_digest(expected, signature):
160
- ResponseResult.fail(message=SIGN_ERROR_MESSAGE, status_code="401", http_code="401")
191
+ _fail_sign("signature_mismatch")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lesscode-flask
3
- Version: 0.2.91
3
+ Version: 0.2.98
4
4
  Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
5
5
  Home-page: https://lesscode-flask
6
6
  Author: Chao.yy
@@ -11,7 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: Flask==3.0.3
14
- Requires-Dist: lesscode-utils==0.0.94
14
+ Requires-Dist: lesscode-utils==0.0.95
15
15
  Requires-Dist: Flask-Login==0.6.3
16
16
  Requires-Dist: redis==5.1.1
17
17
  Requires-Dist: flask-swagger-ui==4.11.1
@@ -24,7 +24,7 @@ Requires-Dist: requests==2.32.3
24
24
  Requires-Dist: gunicorn==23.0.0
25
25
  Requires-Dist: gevent==25.5.1
26
26
  Requires-Dist: openpyxl==3.1.5
27
- Requires-Dist: iputil==0.3.1
27
+ Requires-Dist: iputil==0.3.2
28
28
  Requires-Dist: phone==0.4.5
29
29
  Requires-Dist: psutil==7.2.2
30
30
  Dynamic: author
@@ -62,6 +62,7 @@ lesscode_flask/utils/task/task_helper.py
62
62
  lesscode_flask/utils/thread/thread_utils.py
63
63
  redash/query_runner/__init__.py
64
64
  redash/query_runner/clickhouse.py
65
+ redash/query_runner/dameng.py
65
66
  redash/query_runner/elasticsearch.py
66
67
  redash/query_runner/kingbase.py
67
68
  redash/query_runner/mysql.py
@@ -1,5 +1,5 @@
1
1
  Flask==3.0.3
2
- lesscode-utils==0.0.94
2
+ lesscode-utils==0.0.95
3
3
  Flask-Login==0.6.3
4
4
  redis==5.1.1
5
5
  flask-swagger-ui==4.11.1
@@ -12,6 +12,6 @@ requests==2.32.3
12
12
  gunicorn==23.0.0
13
13
  gevent==25.5.1
14
14
  openpyxl==3.1.5
15
- iputil==0.3.1
15
+ iputil==0.3.2
16
16
  phone==0.4.5
17
17
  psutil==7.2.2
@@ -0,0 +1,218 @@
1
+ import logging
2
+ import threading
3
+
4
+ from redash.query_runner import (BaseSQLQueryRunner, InterruptException, TYPE_DATE, TYPE_DATETIME, TYPE_FLOAT,
5
+ TYPE_INTEGER, TYPE_STRING, register)
6
+
7
+ try:
8
+ import dmPython
9
+
10
+ enabled = True
11
+ except ImportError:
12
+ logging.error("dmPython is not installed, run: pip install dmPython")
13
+ dmPython = None
14
+ enabled = False
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # dmPython type codes mapping to Redash types
19
+ # Reference: dmPython exposes type objects; cursor.description[i][1] returns type code (int)
20
+ # DM8 SQL types: NUMBER(int/float), CHAR/VARCHAR/TEXT(str), DATE, TIMESTAMP, etc.
21
+ types_map = {
22
+ # Numeric
23
+ -7: TYPE_INTEGER, # BIT
24
+ -6: TYPE_INTEGER, # TINYINT
25
+ 5: TYPE_INTEGER, # SMALLINT
26
+ 4: TYPE_INTEGER, # INTEGER / INT
27
+ -5: TYPE_INTEGER, # BIGINT
28
+ 2: TYPE_FLOAT, # NUMERIC
29
+ 3: TYPE_FLOAT, # DECIMAL
30
+ 6: TYPE_FLOAT, # FLOAT
31
+ 7: TYPE_FLOAT, # REAL
32
+ 8: TYPE_FLOAT, # DOUBLE
33
+ # String
34
+ 1: TYPE_STRING, # CHAR
35
+ 12: TYPE_STRING, # VARCHAR
36
+ -1: TYPE_STRING, # LONGVARCHAR / TEXT
37
+ -9: TYPE_STRING, # NVARCHAR
38
+ -10: TYPE_STRING, # LONGNVARCHAR
39
+ # Date/Time
40
+ 91: TYPE_DATE, # DATE
41
+ 92: TYPE_DATETIME, # TIME
42
+ 93: TYPE_DATETIME, # TIMESTAMP / DATETIME
43
+ # Binary / others fall back to string
44
+ -2: TYPE_STRING,
45
+ -3: TYPE_STRING,
46
+ -4: TYPE_STRING,
47
+ }
48
+
49
+
50
+ class Result:
51
+ def __init__(self):
52
+ self.data = None
53
+ self.error = None
54
+
55
+
56
+ class DaMeng(BaseSQLQueryRunner):
57
+ noop_query = "SELECT 1"
58
+
59
+ @classmethod
60
+ def configuration_schema(cls):
61
+ return {
62
+ "type": "object",
63
+ "properties": {
64
+ "host": {"type": "string", "default": "127.0.0.1"},
65
+ "port": {"type": "number", "default": 5236},
66
+ "user": {"type": "string"},
67
+ "password": {"type": "string", "title": "Password"},
68
+ "connect_timeout": {"type": "number", "default": 60, "title": "Connection Timeout"},
69
+ },
70
+ "order": ["host", "port", "user", "password", "connect_timeout"],
71
+ "required": ["host", "user", "password"],
72
+ "secret": ["password"],
73
+ }
74
+
75
+ @classmethod
76
+ def name(cls):
77
+ return "DaMeng"
78
+
79
+ @classmethod
80
+ def type(cls):
81
+ return "dameng"
82
+
83
+ @classmethod
84
+ def enabled(cls):
85
+ return enabled
86
+
87
+ def _connection(self):
88
+ if dmPython is None:
89
+ raise Exception("dmPython is not installed")
90
+ # dmPython.connect accepts: user, password, server (host:port) or host/port separately
91
+ host = self.configuration.get("host", "127.0.0.1")
92
+ port = self.configuration.get("port", 5236)
93
+ user = self.configuration.get("user", "")
94
+ password = self.configuration.get("password", "")
95
+
96
+ connection = dmPython.connect(
97
+ user=user,
98
+ password=password,
99
+ server=host,
100
+ port=int(port)
101
+ )
102
+
103
+ return connection
104
+
105
+ def _get_tables(self, schema):
106
+ database = self.configuration.get("database", "")
107
+
108
+ if database:
109
+ query = """
110
+ SELECT c.OWNER AS table_schema,
111
+ c.TABLE_NAME AS table_name,
112
+ c.COLUMN_NAME AS column_name
113
+ FROM ALL_TAB_COLUMNS c
114
+ WHERE c.OWNER = '{schema}'
115
+ ORDER BY c.OWNER, c.TABLE_NAME, c.COLUMN_ID
116
+ """.format(schema=database.upper())
117
+ else:
118
+ query = """
119
+ SELECT c.OWNER AS table_schema,
120
+ c.TABLE_NAME AS table_name,
121
+ c.COLUMN_NAME AS column_name
122
+ FROM ALL_TAB_COLUMNS c
123
+ ORDER BY c.OWNER, c.TABLE_NAME, c.COLUMN_ID \
124
+ """
125
+
126
+ results, error = self.run_query(query, None)
127
+
128
+ if error is not None:
129
+ self._handle_run_query_error(error)
130
+
131
+ for row in results["rows"]:
132
+ table_schema = row["table_schema"]
133
+ table_name = row["table_name"]
134
+
135
+ if table_schema.upper() != (database or "").upper():
136
+ full_name = "{}.{}".format(table_schema, table_name)
137
+ else:
138
+ full_name = table_name
139
+
140
+ if full_name not in schema:
141
+ schema[full_name] = {"name": full_name, "columns": []}
142
+
143
+ schema[full_name]["columns"].append(row["column_name"])
144
+
145
+ return list(schema.values())
146
+
147
+ def run_query(self, query, user):
148
+ ev = threading.Event()
149
+ r = Result()
150
+
151
+ try:
152
+ connection = self._connection()
153
+ self._run_query(query, user, connection, r, ev)
154
+ except (KeyboardInterrupt, InterruptException):
155
+ raise
156
+
157
+ return r.data, r.error
158
+
159
+ def _convert_column_name(self, column_name, convert_type):
160
+ if convert_type:
161
+ if convert_type == "lower":
162
+ column_name = column_name.lower()
163
+ elif convert_type == "upper":
164
+ column_name = column_name.upper()
165
+ return column_name
166
+
167
+ def _run_query(self, query, user, connection, r, ev):
168
+ cursor = None
169
+ column_name_convert = self.configuration.get("column_name_convert", "lower")
170
+ try:
171
+ cursor = connection.cursor()
172
+ logger.debug("DaMeng running query: %s", query)
173
+ cursor.execute(query)
174
+
175
+ data = cursor.fetchall()
176
+ desc = cursor.description
177
+
178
+ if desc is not None:
179
+ columns = self.fetch_columns(
180
+ [(self._convert_column_name(i[0], column_name_convert), types_map.get(i[1], TYPE_STRING)) for i in
181
+ desc]
182
+ )
183
+ rows = [
184
+ dict(zip((col["name"] for col in columns), row))
185
+ for row in data
186
+ ]
187
+ r.data = {"columns": columns, "rows": rows}
188
+ r.error = None
189
+ else:
190
+ r.data = None
191
+ r.error = "No data was returned."
192
+
193
+ except Exception as e:
194
+ r.data = None
195
+ r.error = str(e)
196
+ finally:
197
+ if cursor:
198
+ cursor.close()
199
+ if connection:
200
+ connection.close()
201
+ ev.set()
202
+
203
+ def exec(self, query, user):
204
+ try:
205
+ connection = self._connection()
206
+ cursor = connection.cursor()
207
+ logger.debug("DaMeng exec query: %s", query)
208
+ cursor.execute(query)
209
+ connection.commit()
210
+ cursor.close()
211
+ connection.close()
212
+ except Exception as e:
213
+ return False, str(e)
214
+
215
+ return True, None
216
+
217
+
218
+ register(DaMeng)