|
| 1 | +# -*- coding: UTF-8 -*- |
| 2 | +from sql.utils.sql_utils import get_syntax_type, remove_comments |
| 3 | +from sql.engines.mysql import MysqlEngine |
| 4 | +from .models import ResultSet, ReviewResult, ReviewSet |
| 5 | +from common.utils.timer import FuncTimer |
| 6 | +from common.config import SysConfig |
| 7 | +from MySQLdb.constants import FIELD_TYPE |
| 8 | +import traceback |
| 9 | +import MySQLdb |
| 10 | +import pymysql |
| 11 | +import sqlparse |
| 12 | +import logging |
| 13 | +import re |
| 14 | + |
| 15 | + |
| 16 | +logger = logging.getLogger("default") |
| 17 | + |
| 18 | + |
| 19 | +class DorisEngine(MysqlEngine): |
| 20 | + name = "Doris" |
| 21 | + info = "Doris engine" |
| 22 | + |
| 23 | + auto_backup = False |
| 24 | + |
| 25 | + @property |
| 26 | + def server_version(self): |
| 27 | + sql = "show frontends" |
| 28 | + result = self.query(sql=sql) |
| 29 | + version = result.rows[0][-1].split("-")[0] |
| 30 | + return tuple([int(n) for n in version.split(".")[:3]]) |
| 31 | + |
| 32 | + def query(self, db_name=None, sql="", limit_num=0, close_conn=True, **kwargs): |
| 33 | + """返回 ResultSet""" |
| 34 | + result_set = ResultSet(full_sql=sql) |
| 35 | + try: |
| 36 | + conn = self.get_connection(db_name=db_name) |
| 37 | + cursor = conn.cursor() |
| 38 | + cursor.execute(sql) |
| 39 | + if int(limit_num) > 0: |
| 40 | + rows = cursor.fetchmany(size=int(limit_num)) |
| 41 | + else: |
| 42 | + rows = cursor.fetchall() |
| 43 | + fields = cursor.description |
| 44 | + |
| 45 | + result_set.column_list = [i[0] for i in fields] if fields else [] |
| 46 | + result_set.rows = rows |
| 47 | + result_set.affected_rows = len(rows) |
| 48 | + except Exception as e: |
| 49 | + logger.warning(f"Doris语句执行报错,语句:{sql},错误信息{e}") |
| 50 | + result_set.error = str(e).split("Stack trace")[0] |
| 51 | + finally: |
| 52 | + if close_conn: |
| 53 | + self.close() |
| 54 | + return result_set |
| 55 | + |
| 56 | + forbidden_databases = [ |
| 57 | + "__internal_schema", |
| 58 | + "INFORMATION_SCHEMA", |
| 59 | + "information_schema", |
| 60 | + ] |
| 61 | + |
| 62 | + def execute_check(self, db_name=None, sql=""): |
| 63 | + """上线单执行前的检查, 返回Review set""" |
| 64 | + check_result = ReviewSet(full_sql=sql) |
| 65 | + # 禁用/高危语句检查 |
| 66 | + line = 1 |
| 67 | + critical_ddl_regex = self.config.get("critical_ddl_regex", "") |
| 68 | + p = re.compile(critical_ddl_regex) |
| 69 | + check_result.syntax_type = 2 # TODO 工单类型 0、其他 1、DDL,2、DML |
| 70 | + for statement in sqlparse.split(sql): |
| 71 | + statement = sqlparse.format(statement, strip_comments=True) |
| 72 | + # 禁用语句 |
| 73 | + if re.match(r"^select|^show|^explain", statement.lower()): |
| 74 | + result = ReviewResult( |
| 75 | + id=line, |
| 76 | + errlevel=2, |
| 77 | + stagestatus="驳回不支持语句", |
| 78 | + errormessage="仅支持DML和DDL语句,查询语句请使用SQL查询功能!", |
| 79 | + sql=statement, |
| 80 | + ) |
| 81 | + # 高危语句 |
| 82 | + elif critical_ddl_regex and p.match(statement.strip().lower()): |
| 83 | + result = ReviewResult( |
| 84 | + id=line, |
| 85 | + errlevel=2, |
| 86 | + stagestatus="驳回高危SQL", |
| 87 | + errormessage="禁止提交匹配" + critical_ddl_regex + "条件的语句!", |
| 88 | + sql=statement, |
| 89 | + ) |
| 90 | + # 驳回未带where数据修改语句,如确实需做全部删除或更新,显示的带上where 1=1 |
| 91 | + elif re.match( |
| 92 | + r"^update((?!where).)*$|^delete((?!where).)*$", statement.lower() |
| 93 | + ): |
| 94 | + result = ReviewResult( |
| 95 | + id=line, |
| 96 | + errlevel=2, |
| 97 | + stagestatus="驳回未带where数据修改", |
| 98 | + errormessage="数据修改需带where条件!", |
| 99 | + sql=statement, |
| 100 | + ) |
| 101 | + # 正常语句 |
| 102 | + else: |
| 103 | + result = ReviewResult( |
| 104 | + id=line, |
| 105 | + errlevel=0, |
| 106 | + stagestatus="Audit completed", |
| 107 | + errormessage="None", |
| 108 | + sql=statement, |
| 109 | + affected_rows=0, |
| 110 | + execute_time=0, |
| 111 | + ) |
| 112 | + # 判断工单类型 |
| 113 | + if get_syntax_type(statement) == "DDL": |
| 114 | + check_result.syntax_type = 1 |
| 115 | + check_result.rows += [result] |
| 116 | + line += 1 |
| 117 | + # 统计警告和错误数量 |
| 118 | + for r in check_result.rows: |
| 119 | + if r.errlevel == 1: |
| 120 | + check_result.warning_count += 1 |
| 121 | + if r.errlevel == 2: |
| 122 | + check_result.error_count += 1 |
| 123 | + return check_result |
| 124 | + |
| 125 | + def execute_workflow(self, workflow): |
| 126 | + return self.execute( |
| 127 | + db_name=workflow.db_name, sql=workflow.sqlworkflowcontent.sql_content |
| 128 | + ) |
| 129 | + |
| 130 | + def execute(self, db_name=None, sql="", close_conn=True): |
| 131 | + """执行sql语句 返回 Review set""" |
| 132 | + execute_result = ReviewSet(full_sql=sql) |
| 133 | + conn = self.get_connection(db_name=db_name) |
| 134 | + rowid = 1 |
| 135 | + effect_row = 0 |
| 136 | + sql_list = sqlparse.split(sql) |
| 137 | + for statement in sql_list: |
| 138 | + try: |
| 139 | + cursor = conn.cursor() |
| 140 | + with FuncTimer() as t: |
| 141 | + effect_row = cursor.execute(statement) |
| 142 | + cursor.close() |
| 143 | + execute_result.rows.append( |
| 144 | + ReviewResult( |
| 145 | + id=rowid, |
| 146 | + errlevel=0, |
| 147 | + stagestatus="Execute Successfully", |
| 148 | + errormessage="None", |
| 149 | + sql=statement, |
| 150 | + affected_rows=effect_row, |
| 151 | + execute_time=t.cost, |
| 152 | + ) |
| 153 | + ) |
| 154 | + except Exception as e: |
| 155 | + logger.warning( |
| 156 | + f"{self.name} 命令执行报错,语句:{sql}, 错误信息:{traceback.format_exc()}" |
| 157 | + ) |
| 158 | + execute_result.error = str(e) |
| 159 | + execute_result.rows.append( |
| 160 | + ReviewResult( |
| 161 | + id=rowid, |
| 162 | + errlevel=2, |
| 163 | + stagestatus="Execute Failed", |
| 164 | + errormessage=f"异常信息:{e}", |
| 165 | + sql=statement, |
| 166 | + affected_rows=effect_row, |
| 167 | + execute_time=t.cost, |
| 168 | + ) |
| 169 | + ) |
| 170 | + break |
| 171 | + rowid += 1 |
| 172 | + if execute_result.error: |
| 173 | + for statement in sql_list[rowid:]: |
| 174 | + execute_result.rows.append( |
| 175 | + ReviewResult( |
| 176 | + id=rowid + 1, |
| 177 | + errlevel=2, |
| 178 | + stagestatus="Audit Completed", |
| 179 | + errormessage="前序语句失败, 未执行", |
| 180 | + sql=statement, |
| 181 | + affected_rows=0, |
| 182 | + execute_time=0, |
| 183 | + ) |
| 184 | + ) |
| 185 | + rowid += 1 |
| 186 | + if close_conn: |
| 187 | + self.close() |
| 188 | + return execute_result |
0 commit comments