From 53063861d49d7689881d20f66881692d5928f48d Mon Sep 17 00:00:00 2001 From: martsforever Date: Tue, 14 Oct 2025 18:43:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=87=86=E5=A4=87=E8=BF=81=E7=A7=BBgen?= =?UTF-8?q?eral=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/general/general_utils/build_delete_sql.py | 18 + app/general/general_utils/build_insert_sql.py | 40 ++ app/general/general_utils/build_query_sql.py | 427 ++++++++++++++++++ app/general/general_utils/build_update_sql.py | 40 ++ app/general/general_utils/sql_service.py | 362 +++++++++++++++ app/general/general_utils/sql_utils.py | 203 +++++++++ 6 files changed, 1090 insertions(+) create mode 100644 app/general/general_utils/build_delete_sql.py create mode 100644 app/general/general_utils/build_insert_sql.py create mode 100644 app/general/general_utils/build_query_sql.py create mode 100644 app/general/general_utils/build_update_sql.py create mode 100644 app/general/general_utils/sql_service.py create mode 100644 app/general/general_utils/sql_utils.py diff --git a/app/general/general_utils/build_delete_sql.py b/app/general/general_utils/build_delete_sql.py new file mode 100644 index 0000000..d60049b --- /dev/null +++ b/app/general/general_utils/build_delete_sql.py @@ -0,0 +1,18 @@ +from app.general.general_utils.sql_utils import log_sql + + +def build_delete_sql(module_config, id): + if isinstance(id, list): + sql = f"delete from {module_config['tableName']} where id in ({','.join(['?'] * len(id))})" + values = id + + else: + sql = f"delete from {module_config['tableName']} where id = ?" + values = [id] + + log_sql(sql, values) + sql = sql.replace("?", "%s") + return (sql, values) + +# sql, values = build_delete_sql(DEMO_MODULE_CONFIG, ['111','2222']) +# log_sql(sql, values) diff --git a/app/general/general_utils/build_insert_sql.py b/app/general/general_utils/build_insert_sql.py new file mode 100644 index 0000000..7a14fb7 --- /dev/null +++ b/app/general/general_utils/build_insert_sql.py @@ -0,0 +1,40 @@ +from app.general.general_utils.sql_utils import format_columns, get_value, get_value_sql, log_sql + + +def build_insert_sql(module_config, row): + column_info = format_columns(module_config["columns"]) + sqls = [f"insert into {get_value(module_config, 'tableName')}"] + values = [] + + field_sql_left_list = [] + field_sql_right_list = [] + field_sql_right_values = [] + + for hump_name, column in column_info['hump_to_columns'].items(): + value = get_value(row, hump_name) + + # if (value === undefined) {return;} + + if value is None: + continue; + + if 't1.' not in column['query']: + continue + + field_sql_left_list.append(column['col_name']) + field_sql_right_list.append(get_value_sql( + value=value, + value_type=column['valueType'], + sql_values=field_sql_right_values, + )) + + sqls.append(f"( {', '.join(field_sql_left_list)} ) ") + sqls.append("values") + sqls.append(f"( {', '.join(field_sql_right_list)} )") + values.extend(field_sql_right_values) + sql = ' '.join(sqls) + log_sql(sql, values) + sql = sql.replace("?", "%s") + return (sql, values) + +# build_insert_sql(DEMO_MODULE_CONFIG, {"normalText": "123", "numberVal": 213}) diff --git a/app/general/general_utils/build_query_sql.py b/app/general/general_utils/build_query_sql.py new file mode 100644 index 0000000..dfd0272 --- /dev/null +++ b/app/general/general_utils/build_query_sql.py @@ -0,0 +1,427 @@ +import re + +from app.general.general_utils.sql_utils import format_columns, get_value, log_sql + + +def query_format(query: str, value_type: str): + if value_type == "string" or value_type == "number": + return query + elif value_type == "date": + return date_format_sql(query) + elif value_type == "datetime": + return datetime_format_sql(query) + elif value_type == "time": + return time_format_sql(query) + + +def date_format_sql(query: str): + return f"date_format({query}, '%Y-%m-%d')" + + +def datetime_format_sql(query: str): + return f"date_format({query}, '%Y-%m-%d %H:%i:%%s')" + + +def time_format_sql(query: str): + return f"date_format({query}, '%H:%i:%%s')" + + +def format_in(value, query, not_in, value_list): + # value_list.append(query) + # 如果 value 不是列表,将其按逗号分割成列表 + if not isinstance(value, list): + list_ = value.split(',') + else: + list_ = value + + # 将 list_ 中的元素添加到 valueList 中 + value_list.extend(list_) + + # 生成格式化字符串 + result = f"{query} {'not ' if not_in else ''}in ({','.join('?' for _ in list_)})" + return result + + +def format_in_like(value, query, not_like, value_list): + # 如果 value 不是列表,将其按逗号分割成列表 + if not isinstance(value, list): + list_ = value.split(',') + else: + list_ = value + + if not_like: + result_list = [] + for item in list_: + value_list.append(f"%{item}%") + result_list.append(f"{query} not like ?") + return f"({' and '.join(result_list)})" + else: + result_list = [] + for item in list_: + value_list.append(f"%{item}%") + result_list.append(f"{query} like ?") + return f"({' or '.join(result_list)})" + + +def format_string2array(val): + return val if isinstance(val, list) else val.split(',') + + +# 测试 convertor +# print(json.dumps(DEMO_QUERY_CONFIG, indent=4)) +# convertor['encode_list'](DEMO_QUERY_CONFIG) +# print(json.dumps(DEMO_QUERY_CONFIG, indent=4)) + +# print(json.dumps(format_columns(DEMO_MODULE_CONFIG['columns']), indent=4)) + +def build_query_sql(query_config, module_config): + column_info = format_columns(module_config["columns"]) + + # print("column_info ==>>", column_info) + + distinct_fields = get_value(query_config, 'distinctFields', []) + + has_distinct = len(distinct_fields) > 0 + + # /*---------------------------------------field sql-------------------------------------------*/ + field_sql_list = [] + field_sql_values = [] + + if has_distinct: + print("has_distinct", distinct_fields) + field_sql_list.append("distinct") + distinct_field_strings = [] + for item_distinct_field in distinct_fields: + item_column_info = get_value(get_value(column_info, 'humpToColumns'), item_distinct_field) + item_field_string = f"{query_format(item_column_info['query'], item_column_info['valueType'])}" + + if not get_value(query_config, 'onlyCount', False): + item_field_string = f"{item_field_string} as '{item_column_info['hump_name']}'" + distinct_field_strings.append(item_field_string) + field_sql_list.append(','.join(distinct_field_strings)) + else: + field_strings = [] + for hump_name, _ in module_config['columns'].items(): + item_column_info = column_info["hump_to_columns"][hump_name] + # 有的humpName是关键词,得用''包裹 + field_strings.append(f"{query_format(item_column_info['query'], item_column_info['valueType'])} as '{item_column_info['hump_name']}'") + field_sql_list.append(','.join(field_strings)) + + # print("field_sql_list ==>>", field_sql_list) + # print("field_sql_values ==>>", field_sql_values) + + # /*---------------------------------------from sql-------------------------------------------*/ + + from_sql_list = ['from'] + from_sql_values = [] + + from_sql_list.append(f"{module_config['tableName']} t1") + + join_config = get_value(module_config, 'joinConfig', []) + if len(join_config): + for item_join_config in join_config: + item_join_config_type = get_value(item_join_config, 'type', '') + if item_join_config_type != 'right join' and item_join_config_type != 'left join' and item_join_config_type != 'join': + raise ValueError(f"Can't recognise join type:{item_join_config_type}") + + from_sql_list.append(f"{item_join_config_type} {item_join_config['table']} {item_join_config['alia']} on {item_join_config['on']}") + + # /*---------------------------------------filter sql-------------------------------------------*/ + + filter_sql_list = [] + filter_sql_values = [] + + query_config_filters = get_value(query_config, 'filters', []) + + if len(query_config_filters): + for index, item_filter in enumerate(query_config_filters): + # print(index, item_filter) + if get_value(item_filter, 'id', None) is None: + item_filter['id'] = f"_{index}" + # print(query_config_filters) + filter_expression = get_value(query_config, 'filterExpression', None) or ' and '.join(item['id'] for item in query_config_filters) + filter_expression = re.sub(r'\s+(并且|&&)\s+', ' and ', filter_expression) + filter_expression = re.sub(r'\s+(或者|\|\|)\s+', ' or ', filter_expression) + + id_2_filter = {item['id']: item for item in query_config_filters} + + def replace_func(match): + + full_match = match.group(0) + filter_id = full_match + + if filter_id == 'and' or filter_id == 'or': + return filter_id + + filter_info = get_value(id_2_filter, filter_id, None) + + if filter_info is None: + return f"[NoMatchFilterForId:{filter_id}]" + + filter_field = filter_info['field'] + item_column = get_value(column_info['hump_to_columns'], filter_field, None) + + if item_column is None: + return f"[NoMatchColumnForField:{filter_field}]" + + filter_type = get_value(filter_info, 'type', None) or get_value(item_column, 'valueType', None) or 'string' + value = get_value(filter_info, 'value', None) + filter_operator = filter_info['operator'] + query = item_column['query'] + + if filter_type == "string": + if filter_operator == '=' or filter_operator == '>' or filter_operator == '>=' or filter_operator == '<' or filter_operator == '<=': + filter_sql_values.append(value) + return f"{query} = ?" + elif filter_operator == '!=': + filter_sql_values.append(value) + return f"{query} != ?" + elif filter_operator == '~': + filter_sql_values.append(f"%{value}%") + return f"{query} like ?" + elif filter_operator == 'in': + return format_in(value, query, False, filter_sql_values) + elif filter_operator == 'not in': + return format_in(value, query, True, filter_sql_values) + elif filter_operator == 'in like': + return format_in_like(value, query, False, filter_sql_values) + elif filter_operator == 'not in like': + return format_in_like(value, query, True, filter_sql_values) + elif filter_operator == 'is null': + return f"{query} is null" + elif filter_operator == 'is not null': + return f"{query} is not null" + elif filter_type == "number": + if filter_operator == '=': + filter_sql_values.append(value) + return f"{query} = ?" + elif filter_operator == '!=': + filter_sql_values.append(value) + return f"{query} != ?" + elif filter_operator == '~': + filter_sql_values.append(f"%{value}%") + return f"{query} like ?" + elif filter_operator == '>': + filter_sql_values.append(value) + return f"{query} > ?" + elif filter_operator == '>=': + filter_sql_values.append(value) + return f"{query} >= ?" + elif filter_operator == '<': + filter_sql_values.append(value) + return f"{query} < ?" + elif filter_operator == '<=': + filter_sql_values.append(value) + return f"{query} <= ?" + elif filter_operator == 'in': + return format_in(value, query, False, filter_sql_values) + elif filter_operator == 'not in': + return format_in(value, query, True, filter_sql_values) + elif filter_operator == 'in like': + return format_in_like(value, query, False, filter_sql_values) + elif filter_operator == 'not in like': + return format_in_like(value, query, True, filter_sql_values) + elif filter_operator == 'is null': + return f"{query} is null" + elif filter_operator == 'is not null': + return f"{query} is not null" + elif filter_type == "date": + if filter_operator == '=' or filter_operator == '~': + filter_sql_values.append(value) + return f"{date_format_sql(query)} = ?" + elif filter_operator == '!=': + filter_sql_values.append(value) + return f"{date_format_sql(query)} != ?" + elif filter_operator == '>': + filter_sql_values.append(value) + return f"{query} > ?" + elif filter_operator == '>=': + filter_sql_values.append(value) + return f"{query} >= ?" + elif filter_operator == '<': + filter_sql_values.append(value) + return f"{query} < ?" + elif filter_operator == '<=': + filter_sql_values.append(value) + return f"{query} <= ?" + elif filter_operator == 'in' or filter_operator == 'in like': + v_list = format_string2array(value) + filter_sql_values.extend(v_list) + return f"{date_format_sql(query)} in ({','.join('?' for _ in v_list)})" + elif filter_operator == 'not in' or filter_operator == 'not in like': + v_list = format_string2array(value) + filter_sql_values.extend(v_list) + return f"{date_format_sql(query)} not in ({','.join('?' for _ in v_list)})" + elif filter_operator == 'is null': + return f"{query} is null" + elif filter_operator == 'is not null': + return f"{query} is not null" + elif filter_type == "time": + if filter_operator == '=' or filter_operator == '~': + filter_sql_values.append(value) + return f"{time_format_sql(query)} = ?" + elif filter_operator == '!=': + filter_sql_values.append(value) + return f"{time_format_sql(query)} != ?" + elif filter_operator == '>': + filter_sql_values.append(value) + return f"{query} > ?" + elif filter_operator == '>=': + filter_sql_values.append(value) + return f"{query} >= ?" + elif filter_operator == '<': + filter_sql_values.append(value) + return f"{query} < ?" + elif filter_operator == '<=': + filter_sql_values.append(value) + return f"{query} <= ?" + elif filter_operator == 'in' or filter_operator == 'in like': + v_list = format_string2array(value) + filter_sql_values.extend(v_list) + return f"{time_format_sql(query)} in ({','.join('?' for _ in v_list)})" + elif filter_operator == 'not in' or filter_operator == 'not in like': + v_list = format_string2array(value) + filter_sql_values.extend(v_list) + return f"{time_format_sql(query)} not in ({','.join('?' for _ in v_list)})" + elif filter_operator == 'is null': + return f"{query} is null" + elif filter_operator == 'is not null': + return f"{query} is not null" + return + elif filter_type == "datetime": + if filter_operator == '=' or filter_operator == '~': + filter_sql_values.append(value) + return f"{datetime_format_sql(query)} = ?" + elif filter_operator == '!=': + filter_sql_values.append(value) + return f"{datetime_format_sql(query)} != ?" + elif filter_operator == '>': + filter_sql_values.append(value) + return f"{query} > ?" + elif filter_operator == '>=': + filter_sql_values.append(value) + return f"{query} >= ?" + elif filter_operator == '<': + filter_sql_values.append(value) + return f"{query} < ?" + elif filter_operator == '<=': + filter_sql_values.append(value) + return f"{query} <= ?" + elif filter_operator == 'not in' or filter_operator == 'not in like': + v_list = format_string2array(value) + filter_sql_values.extend(v_list) + return f"{datetime_format_sql(query)} in ({','.join('?' for _ in v_list)})" + elif filter_operator == 'in' or filter_operator == 'in like': + v_list = format_string2array(value) + filter_sql_values.extend(v_list) + return f"{datetime_format_sql(query)} not in ({','.join('?' for _ in v_list)})" + elif filter_operator == 'is null': + return f"{query} is null" + elif filter_operator == 'is not null': + return f"{query} is not null" + return + else: + return f"NoMatchFilterType:{filter_type}" + + return f"filter type {filter_type} no match operator: ${filter_operator}" + + new_filter_expression = re.sub(r'[a-zA-Z0-9_-]+', replace_func, filter_expression) + + filter_sql_list.extend(['where', new_filter_expression]) + + # /*---------------------------------------only count-------------------------------------------*/ + + sqls = [] + values = [] + + query_config_only_count = get_value(query_config, 'onlyCount', False) + + if query_config_only_count: + if not has_distinct: + sqls.append("select count(0) as total") + else: + sqls.append(f"select count( {' '.join(field_sql_list)} ) as total") + values.extend(field_sql_values) + + sqls.extend(from_sql_list) + values.extend(from_sql_values) + + sqls.extend(filter_sql_list) + values.extend(filter_sql_values) + else: + sqls.append('select') + + sqls.extend(field_sql_list) + values.extend(field_sql_values) + + sqls.extend(from_sql_list) + values.extend(from_sql_values) + + sqls.extend(filter_sql_list) + values.extend(filter_sql_values) + + def get_sort_sql_value(): + sort_sql_list = [] + sort_sql_values = [] + + query_config_orders = get_value(query_config, 'orders', []) + query_config_orders = query_config_orders if isinstance(query_config_orders, list) else [query_config_orders] + + if len(query_config_orders) > 0: + sort_sql_list.append("order by") + temp_list = [] + + for sort_item in query_config_orders: + sn = '' + sc = '' + + if isinstance(sort_item, str): + sn = sort_item + sc = 'desc' + else: + sn = sort_item['field'] + sc = 'desc' if sort_item['desc'] else 'asc' + + column_item = get_value(column_info['hump_to_columns'], sn, None) + + if column_item is None: + temp_list.append(f"[NoMatchSortField:{sn}]") + else: + temp_list.append(f"{column_item['query']} {sc}") + + sort_sql_list.append(', '.join(temp_list)) + return (sort_sql_list, sort_sql_values) + + sort_sql_list, sort_sql_values = get_sort_sql_value() + + sqls.extend(sort_sql_list) + values.extend(sort_sql_values) + + query_config_all = get_value(query_config, 'all', False) + + if not query_config_all: + sqls.append("limit ?,?") + values.extend([ + get_value(query_config, 'offset', 0), + get_value(query_config, 'size', 10) + ]) + # /*---------------------------------------end-------------------------------------------*/ + + sql = ' '.join(sqls) + log_sql(sql, values) + sql = sql.replace("?", "%s") + + return (sql, values) + +# sql, values = build_query_sql(DEMO_MODULE_CONFIG, { +# "page": 0, +# "size": 10, +# "filters": [ +# { +# "field": "count", +# "operator": "is null", +# "id": "query_meta_1" +# } +# ] +# }) +# log_sql(sql, values) diff --git a/app/general/general_utils/build_update_sql.py b/app/general/general_utils/build_update_sql.py new file mode 100644 index 0000000..e71d98f --- /dev/null +++ b/app/general/general_utils/build_update_sql.py @@ -0,0 +1,40 @@ +from datetime import datetime + +from app.general.general_utils.sql_utils import get_value, format_columns, get_value_sql, log_sql + +UPDATE_EXCLUDE_FIELDS = ['id', 'createAt', 'createdBy'] + + +def build_update_sql(module_config, row, param_update_fields=None): + row_id = get_value(row, 'id', None) + + if row_id is None: + raise Exception("row_id is None") + + column_info = format_columns(module_config["columns"]) + sqls = [f"update {module_config['tableName']} set"] + values = [] + + field_sql_list = [] + for hump_name, column in column_info['hump_to_columns'].items(): + value = get_value(row, hump_name, None) + if hump_name in UPDATE_EXCLUDE_FIELDS: + continue + if not column['query'].startswith('t1.'): + continue + if param_update_fields is not None and hump_name not in param_update_fields: + # 如果有指定更新的字段,并且humpName不在这个字段列表中,则不更新这个字段 + continue + if hump_name == 'updateAt': + value = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + field_sql_list.append(f"{column['col_name']} = {get_value_sql(value=value, value_type=column['valueType'], sql_values=values, )}") + sqls.append(', '.join(field_sql_list)) + sqls.append("where id = ?") + values.append(row_id) + + sql = ' '.join(sqls) + log_sql(sql, values) + sql = sql.replace("?", "%s") + return (sql, values) + +# build_update_sql(DEMO_MODULE_CONFIG, DEMO_QUERY_ROWS[0],['normalText','numberVal']) diff --git a/app/general/general_utils/sql_service.py b/app/general/general_utils/sql_service.py new file mode 100644 index 0000000..5c5426d --- /dev/null +++ b/app/general/general_utils/sql_service.py @@ -0,0 +1,362 @@ +from app.general.general_utils.build_delete_sql import build_delete_sql +from app.general.general_utils.build_insert_sql import build_insert_sql +from app.general.general_utils.build_query_sql import build_query_sql +from app.general.general_utils.build_update_sql import build_update_sql +from app.general.general_utils.sql_utils import get_value, create_convertor + + +def get_default_orders(query_config, module_config): + orders = get_value(query_config, 'orders', None) + if orders is not None: + return orders + module_config_default_orders = get_value( + get_value(module_config, 'default', {}), + 'orders', + None + ) + if module_config_default_orders is not None: + return module_config_default_orders + return {"field": "createdAt", "desc": True} + + +def get_id(cursor, len): + if len is None: + len = 1 + sql = f"select {','.join([f'uuid() as _{idx}' for idx in range(len)])}" + cursor.execute(sql) + rows = cursor.fetchall() + + columns = [column[0] for column in cursor.description] + result = [dict(zip(columns, row)) for row in rows] + + return [val for key, val in result[0].items()] + + +def list(connect_config, query_config, module_config, debug_data=[]): + conn, cursor, close = get_sql_connection(connect_config) + + n_page = get_value(query_config, 'page', 0) + n_size = get_value(query_config, 'size', 5) + n_only_count = get_value(query_config, 'onlyCount', False) + + offset = n_page * n_size + # 多查一条数据,方便判断是否有下一页数据 + size = n_size + 1 + + target_query_config = { + **query_config, + "offset": offset, + "size": size, + "orders": get_default_orders(query_config, module_config) + } + + sql, values = build_query_sql(target_query_config, module_config) + + try: + + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + rows = cursor.fetchall() + + # 将查询结果转换为字典列表 + columns = [column[0] for column in cursor.description] + result = [dict(zip(columns, row)) for row in rows] + + create_convertor(module_config)['decode_list'](result) + + if n_only_count: + return { + "total": result[0]['total'] + } + else: + has_next = False if get_value(query_config, 'all', False) else len(result) == n_size + 1 + if has_next: + result.pop() + return { + "hasNext": has_next, + "list": result, + } + except mysql.connector.Error as err: + return { + "error": f"Error: {err}", + } + finally: + close() + + +def item(connect_config, query_config, module_config, debug_data=[]): + target_query_config = { + "offset": 0, + "size": 1, + "filters": [], + "orders": {"field": "createdAt", "desc": True}, + } + for humpName, value in query_config.items(): + target_query_config['filters'].append({ + "field": humpName, + "value": value, + "operator": "=" + }) + + result = list(connect_config=connect_config, query_config=target_query_config, module_config=module_config, debug_data=debug_data) + + if "error" in result: + return result + + return {"result": None if "list" not in result or len(result['list']) == 0 else result['list'][0]} + + +def insert(connect_config, query_config, module_config, debug_data=[]): + conn, cursor, close = get_sql_connection(connect_config) + + row = get_value(query_config, 'row', None) + if row is None: + return { + "error": "row parameter is missing", + } + create_convertor(module_config)['encode_list']([row]) + + row_id = get_value(row, 'id', None) + + if row_id is None: + row_id = get_id(cursor, 1)[0] + row['id'] = row_id + + try: + sql, values = build_insert_sql(module_config, row) + + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + conn.commit() + + sql, values = build_query_sql({"page": 0, "size": 1, "filters": [{"field": "id", "operator": "=", "value": row_id}]}, module_config) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + rows = cursor.fetchall() + + columns = [column[0] for column in cursor.description] + result = [dict(zip(columns, row)) for row in rows] + + create_convertor(module_config)['decode_list'](result) + + if len(result) > 0: + return { + "result": result[0] + } + else: + return { + "error": "insert failed, query result is empty", + } + + except mysql.connector.Error as err: + return { + "error": f"Error: {err}", + } + finally: + close() + + +def batch_insert(connect_config, query_config, module_config, debug_data=[]): + conn, cursor, close = get_sql_connection(connect_config) + + rows = get_value(query_config, 'rows', None) + if rows is None or len(rows) == 0: + return { + "error": "rows parameter is missing", + } + create_convertor(module_config)['encode_list'](rows) + + row_id_list = [] + for row in rows: + row_id = get_value(row, 'id', None) + if row_id is None: + row_id = get_id(cursor, 1)[0] + row['id'] = row_id + row_id_list.append(row_id) + + try: + for row in rows: + sql, values = build_insert_sql(module_config, row) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + + conn.commit() + + sql, values = build_query_sql({"all": True, "filters": [{"field": "id", "operator": "in", "value": row_id_list}]}, module_config) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + rows = cursor.fetchall() + + columns = [column[0] for column in cursor.description] + result = [dict(zip(columns, row)) for row in rows] + + create_convertor(module_config)['decode_list'](result) + + if len(result) > 0: + return { + "result": result + } + else: + return { + "error": "insert failed, query result is empty", + } + + except mysql.connector.Error as err: + return { + "error": f"Error: {err}", + } + finally: + close() + + +def update(connect_config, query_config, module_config, debug_data=[]): + conn, cursor, close = get_sql_connection(connect_config) + + row = get_value(query_config, 'row', None) + update_fields = get_value(query_config, 'updateFields', None) + + if row is None: + return { + "error": "row parameter is missing", + } + + row_id = get_value(row, 'id', None) + + if row_id is None: + return { + "error": "row is missing field: id", + } + + create_convertor(module_config)['encode_list']([row]) + + try: + sql, values = build_update_sql(module_config, row, update_fields) + + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + conn.commit() + + sql, values = build_query_sql({"page": 0, "size": 1, "filters": [{"field": "id", "operator": "=", "value": row_id}]}, module_config) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + rows = cursor.fetchall() + + columns = [column[0] for column in cursor.description] + result = [dict(zip(columns, row)) for row in rows] + + create_convertor(module_config)['decode_list'](result) + + if len(result) > 0: + return { + "result": result[0] + } + else: + return { + "error": "update failed, query result is empty", + } + + except mysql.connector.Error as err: + return { + "error": f"Error: {err}", + } + finally: + close() + + +def batch_update(connect_config, query_config, module_config, debug_data=[]): + conn, cursor, close = get_sql_connection(connect_config) + + rows = get_value(query_config, 'rows', None) + update_fields = get_value(query_config, 'updateFields', None) + + if rows is None or len(rows) == 0: + return { + "error": "rows parameter is missing", + } + create_convertor(module_config)['encode_list'](rows) + + row_id_list = [] + for row in rows: + row_id = get_value(row, 'id', None) + if row_id is None: + return { + "error": "row is missing field: id", + "row": row, + } + row_id_list.append(row_id) + + try: + for row in rows: + sql, values = build_update_sql(module_config, row, update_fields) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + + conn.commit() + + sql, values = build_query_sql({"all": True, "filters": [{"field": "id", "operator": "in", "value": row_id_list}]}, module_config) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + rows = cursor.fetchall() + + columns = [column[0] for column in cursor.description] + result = [dict(zip(columns, row)) for row in rows] + + create_convertor(module_config)['decode_list'](result) + + if len(result) > 0: + return { + "result": result + } + else: + return { + "error": "update failed, query result is empty", + } + + except mysql.connector.Error as err: + return { + "error": f"Error: {err}", + } + finally: + close() + + +def delete(connect_config, query_config, module_config, debug_data=[]): + conn, cursor, close = get_sql_connection(connect_config) + id = get_value(query_config, 'id', None) + if id is None: + return { + "error": "id parameter is missing", + } + + try: + sql, values = build_delete_sql(module_config, id) + debug_data.append({"sql": sql, "values": values}) + cursor.execute(sql, values) + conn.commit() + + deleted_rows = cursor.rowcount + + if deleted_rows >= 1: + return {"deletedRows": deleted_rows} + else: + return {"error": f"delete failed, delete rows is {deleted_rows}", } + except mysql.connector.Error as err: + return { + "error": f"Error: {err}", + } + finally: + close() + + +class SqlService(): + def __init__(self): + self.list = list + self.item = item + self.insert = insert + self.update = update + self.delete = delete + self.batch_insert = batch_insert + self.batch_update = batch_update + + +sql_service = SqlService() diff --git a/app/general/general_utils/sql_utils.py b/app/general/general_utils/sql_utils.py new file mode 100644 index 0000000..517c8dc --- /dev/null +++ b/app/general/general_utils/sql_utils.py @@ -0,0 +1,203 @@ +import json +import re + + +# 将json字符串中的 \u00A0 全部去掉,\u00A0 表示  ,当这个字符存在的时候会导致无法将json字符串合法地解析为对象/字典 +def format_json_string(json_str): + json_str = re.sub(r'\u00A0', '', json_str) + return json_str + + +# 将值转化为json字符串 +def array_json_encoder(val): + return json.dumps(val, ensure_ascii=False) + + +# 将json字符串转为数组 +def array_json_decoder(val): + try: + return json.loads(val) + except json.JSONDecodeError: + return "" + + +# 将数组转为逗号连接的字符串 +def array_string_encoder(val): + return ",".join(val) if isinstance(val, list) else val + + +# 将逗号连接的字符串转换为数组 +def array_string_decoder(val): + return val.split(",") if isinstance(val, str) else val + + +# 值格式化类型arrayjson +MODULE_CONVERT_TYPE_ARRAY_JSON = 'arrayjson' +# 值格式化类型arraystring +MODULE_CONVERT_TYPE_ARRAY_STRING = 'arraystring' + +# 转化工具 +ConvertTypes = { + "arrayjson": { + "encode": array_json_encoder, + "decode": array_json_decoder, + }, + "arraystring": { + "encode": array_string_encoder, + "decode": array_string_decoder, + } +} + + +# 根据module_config创建一个转化器 +def create_convertor(config): + convert_columns = [(col_name, col_config) for col_name, col_config in config["columns"].items() if col_config.get("convert")] + + # print('convert_columns ==>>', convert_columns) + + # 将值转化为字符串 + def encode_list(list_): + if not len(convert_columns): + return + for item in list_: + for col_name, col_config in convert_columns: + if get_value(item, col_name, None) is not None: + if not isinstance(item[col_name], str): + item[col_name] = ConvertTypes[col_config["convert"]]["encode"](item[col_name]) + + # 将字符串转化为值 + def decode_list(list_): + if not len(convert_columns): + return + for item in list_: + for col_name, col_config in convert_columns: + if get_value(item, col_name, None) is not None: + if isinstance(item[col_name], str): + item[col_name] = ConvertTypes[col_config["convert"]]["decode"](item[col_name]) + + return { + "encode_list": encode_list, + "decode_list": decode_list + } + + +# 将驼峰命名转换为下划线命名 +def to_line(hump_name: str) -> str: + return re.sub(r'([A-Z])', r'_\1', hump_name).lower() + + +# 格式化字段信息 +def format_columns(columns): + # 通过驼峰命名找到字段信息 + hump_to_columns = {} + # 通过下划线命名找到字段信息 + line_to_columns = {} + + for hump_name, col_config in columns.items(): + line_name = to_line(hump_name) + query = get_value(col_config, "query", None) or f"t1.{line_name}" + info = { + **col_config, + "hump_name": hump_name, + "line_name": line_name, + "query": query, + "col_name": query.split('.')[1] + } + hump_to_columns[hump_name] = info + line_to_columns[line_name] = info + return { + # 通过下划线命令查找列信息 + "hump_to_columns": hump_to_columns, + # 通过驼峰命名查找列信息 + "line_to_columns": line_to_columns, + } + + +# 通用的获取属性值的方法 +def get_value(obj, attr_name, default=None): + if isinstance(obj, dict): + return obj.get(attr_name, default) + else: + return getattr(obj, attr_name, default) + + +# 获取值的sql查询语句 +def get_value_sql(value, value_type, sql_values): + if value_type == 'string' or value_type == 'number': + sql_values.append(value) + return '?' + elif value_type == 'date': + sql_values.append(value) + return "str_to_date(?, '%Y-%m-%d')" + elif value_type == 'datetime': + sql_values.append(value) + return "str_to_date(?, '%Y-%m-%d %H:%i:%%s')" + elif value_type == 'time': + sql_values.append(value) + return "str_to_date(?, '%H:%i:%%s')"; + + +def parse_env_content(env_content): + """ + 将.env文件内容解析为字典格式 + + Args: + env_content (str): .env文件的内容字符串 + + Returns: + dict: 包含所有环境变量的字典 + """ + config_dict = {} + + # 按行分割内容 + lines = env_content.strip().split('\n') + + # 定义注释和值的正则表达式 + pattern = r'^([^=#]+)=([^#]*)(?:#.*)?$' + + for line in lines: + line = line.strip() + if line and not line.startswith('#'): # 忽略空行和纯注释行 + match = re.match(pattern, line) + if match: + key = match.group(1).strip() + value = match.group(2).strip() + # 移除可能存在的引号 + if value.startswith('"') and value.endswith('"'): + value = value[1:-1] + elif value.startswith("'") and value.endswith("'"): + value = value[1:-1] + config_dict[key] = value + + return config_dict + + +show_sql = True + + +# 一个用于打印sql的工具函数 +def log_sql(sql, values): + if show_sql: + print("\n/*---------------------------------------log sql-------------------------------------------*/\n") + print("\nsource sql-->>\n") + print(sql) + print("\nsql params-->>\n") + print(values) + count = 0 + + def replace_callback(match): + nonlocal count + val = values[count] + count = count + 1 + if isinstance(val, str): + return val + if isinstance(val, list): + return ', '.join(map(str, val)) + # formatDebugData 要加上'',不然有些关键词没有''当做字符串的话会报错 + return f"'{str(val)}'" + + import re + target_sql = re.sub(r'\?+', replace_callback, sql) + print("\ntarget sql-->>\n") + print(target_sql) + print("\n")