3094 lines
92 KiB
Python
3094 lines
92 KiB
Python
|
|
# Easy Sqlite Toolkit
|
|||
|
|
# Author Zhj<2022-12-06>
|
|||
|
|
|
|||
|
|
import typing
|
|||
|
|
import fcntl
|
|||
|
|
import os
|
|||
|
|
import sqlite3
|
|||
|
|
import time
|
|||
|
|
import traceback
|
|||
|
|
import re
|
|||
|
|
import copy
|
|||
|
|
import weakref
|
|||
|
|
from functools import reduce
|
|||
|
|
from contextlib import contextmanager
|
|||
|
|
|
|||
|
|
import public
|
|||
|
|
from .gcmanager import gc_enable, gc_disable
|
|||
|
|
from .tools import is_number
|
|||
|
|
from .exceptions import HintException, PanelError
|
|||
|
|
|
|||
|
|
|
|||
|
|
_BASE_DIR = '/www/server/panel'
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 记录错误日志
|
|||
|
|
def _log(e):
|
|||
|
|
if isinstance(e, HintException):
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 日志目录
|
|||
|
|
log_dir = '{}/logs/sqlite_easy/{}'.format(_BASE_DIR, time.strftime('%Y%m'))
|
|||
|
|
|
|||
|
|
# 确保目录已创建
|
|||
|
|
if not os.path.exists(log_dir):
|
|||
|
|
os.makedirs(log_dir, 0o600)
|
|||
|
|
|
|||
|
|
# 记录错误日志
|
|||
|
|
with open('{}/{}.log'.format(log_dir, time.strftime('%d')), 'a') as fp:
|
|||
|
|
fp.write('[{}]{}\n'.format(time.strftime('%Y-%m-%d %X'), str(e)))
|
|||
|
|
|
|||
|
|
# 如果是异常,打印异常堆栈信息
|
|||
|
|
if isinstance(e, BaseException):
|
|||
|
|
fp.write('{}\n'.format(traceback.format_exc()))
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 日志打印
|
|||
|
|
def _log2(s, log_file='sqlite_client.log'):
|
|||
|
|
log_dir = '{}/logs'.format(_BASE_DIR)
|
|||
|
|
|
|||
|
|
# 确保目录已创建
|
|||
|
|
if not os.path.exists(log_dir):
|
|||
|
|
os.makedirs(log_dir, 0o600)
|
|||
|
|
|
|||
|
|
with open('{}/{}'.format(log_dir, log_file), 'a') as fp:
|
|||
|
|
fp.write('[{}]{}\n'.format(time.strftime('%Y-%m-%d %X'), s))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _to_tuple(p):
|
|||
|
|
'''
|
|||
|
|
@name 将输入参数转为元组
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param p<mixed> 输入参数
|
|||
|
|
@return tuple
|
|||
|
|
'''
|
|||
|
|
# 输入参数为元组时 直接返回
|
|||
|
|
if isinstance(p, tuple):
|
|||
|
|
return p
|
|||
|
|
|
|||
|
|
# 输入参数为列表时 转为元组
|
|||
|
|
if isinstance(p, list):
|
|||
|
|
return tuple(p)
|
|||
|
|
|
|||
|
|
# 其他情况 直接创建一个元组 并将参数放入其中
|
|||
|
|
return (p,)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 正则表达式列表
|
|||
|
|
# 匹配SQL字段名称的正则表达式
|
|||
|
|
match_field_reg = re.compile(r'^(?:`?(\w+)`?\.)?`?(\w+)`?$', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
# 匹配表名的正则表达式
|
|||
|
|
match_table_name_reg = re.compile(r'^([\w`]+)(?:(?:\s+AS\s+|\s+)([\w`]+))?$', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
# 匹配【?】号的正则表达式
|
|||
|
|
search_question_reg = re.compile(r'\?')
|
|||
|
|
|
|||
|
|
# 匹配查询字段名称的正则表达式
|
|||
|
|
match_row_key_reg = re.compile(r'^(?:distinct\s+)?(?:[\w`]+\.)?([\w`]+)(?:(?:\s+AS\s+|\s+)([\w`]+))?$', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
# 匹配查询字段别名的正则表达式
|
|||
|
|
search_row_key_reg = re.compile(r'(?:\s+AS\s+|\s+)([\w`]+)$', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
# 匹配SQL逻辑操作符【AND】【OR】的正则表达式
|
|||
|
|
search_logic_opt_reg = re.compile(r'\s+(?:AND|OR)\s+', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
# 匹配以【1 AND 】开头的WHERE语句的正则表达式
|
|||
|
|
match_where_str_begin_reg = re.compile(r'^1\s+(?:AND|OR)\s+', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
# 匹配引号【'】【"】的正则表达式
|
|||
|
|
search_quote_reg = re.compile(r'(?<!\\)([\'"])')
|
|||
|
|
|
|||
|
|
# 匹配子查询的正则表达式
|
|||
|
|
search_subquery_reg = re.compile(r'^(\([\s\S]+?\))(?:(?:\s+AS\s+|\s+)([\w`]+))?$', flags=re.IGNORECASE)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _add_backtick_for_field(field):
|
|||
|
|
'''
|
|||
|
|
@name 给字段名添加反引号
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
return match_field_reg.sub(lambda m: '{}{}'.format(
|
|||
|
|
'' if m.group(1) is None else '`%s`.' % m.group(1),
|
|||
|
|
'`%s`' % m.group(2)
|
|||
|
|
), str(field).strip())
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _format_cursor(cursor):
|
|||
|
|
'''
|
|||
|
|
@name 将cursor对象转换为list对象
|
|||
|
|
@author Zhj<2023-04-25>
|
|||
|
|
@param cursor<sqlite.Cursor> 游标对象
|
|||
|
|
@return list
|
|||
|
|
'''
|
|||
|
|
gc_disable()
|
|||
|
|
cols = tuple(map(lambda x: x[0], cursor.description))
|
|||
|
|
data = []
|
|||
|
|
for row in cursor:
|
|||
|
|
d = {}
|
|||
|
|
i = 0
|
|||
|
|
for col in cols:
|
|||
|
|
d[col] = row[i]
|
|||
|
|
i += 1
|
|||
|
|
data.append(d)
|
|||
|
|
cursor.close()
|
|||
|
|
gc_enable()
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
|
|||
|
|
@contextmanager
|
|||
|
|
def _auto_repair_context(db_path):
|
|||
|
|
'''
|
|||
|
|
@name 自动修复sqlite数据库的上下文环境
|
|||
|
|
@author Zhj<2023-03-15>
|
|||
|
|
@param db_path<string> sqlite数据库绝对路径
|
|||
|
|
@return void
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
# os.chdir(_BASE_DIR)
|
|||
|
|
# sys.path.insert(0, _BASE_DIR)
|
|||
|
|
#
|
|||
|
|
# import core.include.public as public
|
|||
|
|
# path = os.getcwd()
|
|||
|
|
# monitor_backpath = path + '/backupDB/monitor_mgr_bak.db'
|
|||
|
|
# safety_backpath = path + '/backupDB/safety_bak.db'
|
|||
|
|
try:
|
|||
|
|
yield
|
|||
|
|
except BaseException as e:
|
|||
|
|
if str(db_path).startswith(':memory:'):
|
|||
|
|
raise e
|
|||
|
|
|
|||
|
|
if str(e) in ['database disk image is malformed', 'file is not a database']:
|
|||
|
|
# TODO 修复数据库
|
|||
|
|
# public.ExecShell('\cp -rf %s %s' % (monitor_backpath,db_path))
|
|||
|
|
# public.ExecShell('\cp -rf %s %s' % (safety_backpath,db_path))
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
raise e
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Where:
|
|||
|
|
'''
|
|||
|
|
@name where条件收集类
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__WHERE_STR', '__BIND_PARAMS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__WHERE_STR = '1'
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def add(self, condition, bind_params=(), logic='AND'):
|
|||
|
|
'''
|
|||
|
|
@name 添加where条件
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param condition<string> where条件
|
|||
|
|
@param bind_params<tuple|list> 绑定参数
|
|||
|
|
@param logic<string> 逻辑运算符 AND|OR
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__judge_the_logic(logic)
|
|||
|
|
|
|||
|
|
# 当检测condition为字段名且存在参数绑定时,自动添加"=?"
|
|||
|
|
if match_field_reg.match(condition) and len(_to_tuple(bind_params)) > 0:
|
|||
|
|
condition = '{} = ?'.format(_add_backtick_for_field(condition))
|
|||
|
|
|
|||
|
|
where_str = ' {} ({})' if search_logic_opt_reg.search(condition) else ' {} {}'
|
|||
|
|
self.__WHERE_STR += where_str.format(logic.upper(), str(condition))
|
|||
|
|
self.__BIND_PARAMS += _to_tuple(bind_params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def add_where_in(self, field, vals, logic='AND', not_in=False):
|
|||
|
|
'''
|
|||
|
|
@name 添加where IN查询条件
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param vals<list|tuple> 查询条件
|
|||
|
|
@param logic<string> 逻辑运算符 AND|OR
|
|||
|
|
@param not_in<bool> 是否为NOT IN
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__judge_the_logic(logic)
|
|||
|
|
|
|||
|
|
if isinstance(vals, str) or isinstance(vals, int) or isinstance(vals, float):
|
|||
|
|
vals = [vals]
|
|||
|
|
|
|||
|
|
# 空列表
|
|||
|
|
# (IN)构建where 0
|
|||
|
|
# (NOT IN)构建where 1
|
|||
|
|
if len(vals) == 0:
|
|||
|
|
self.__WHERE_STR += ' {} {}'.format(logic.upper(), 1 if not_in else 0)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
# 元组转列表
|
|||
|
|
if isinstance(vals, tuple):
|
|||
|
|
vals = list(vals)
|
|||
|
|
|
|||
|
|
# 去重
|
|||
|
|
vals = list(set(vals))
|
|||
|
|
|
|||
|
|
# 构造where条件
|
|||
|
|
tmp = []
|
|||
|
|
where_params = ()
|
|||
|
|
|
|||
|
|
# 绑定参数过多时使用字符串拼接
|
|||
|
|
is_to_more_vals = len(vals) > 300
|
|||
|
|
|
|||
|
|
if is_to_more_vals:
|
|||
|
|
for val in vals:
|
|||
|
|
if is_number(val):
|
|||
|
|
tmp.append(str(val))
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
tmp.append("'{}'".format(search_quote_reg.sub(r'\\\1', str(val))))
|
|||
|
|
else:
|
|||
|
|
for val in vals:
|
|||
|
|
tmp.append('?')
|
|||
|
|
where_params += (val,)
|
|||
|
|
|
|||
|
|
self.__WHERE_STR += ' {} {} {} ({})'.format(
|
|||
|
|
logic.upper(),
|
|||
|
|
_add_backtick_for_field(field),
|
|||
|
|
'IN' if not not_in else 'NOT IN',
|
|||
|
|
','.join(tmp)
|
|||
|
|
)
|
|||
|
|
self.__BIND_PARAMS += where_params
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def add_nest(self, where_obj, logic='AND'):
|
|||
|
|
self.__judge_the_logic(logic)
|
|||
|
|
|
|||
|
|
if not isinstance(where_obj, Where):
|
|||
|
|
raise RuntimeError('parameter where_obj must a type of Where')
|
|||
|
|
|
|||
|
|
self.__WHERE_STR += ' {} ({})'.format(logic.upper(), where_obj.to_string())
|
|||
|
|
self.__BIND_PARAMS += where_obj.get_bind_params()
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def __judge_the_logic(self, logic):
|
|||
|
|
if logic.upper() not in ['AND', 'OR']:
|
|||
|
|
raise RuntimeError('parameter logic must be AND or OR')
|
|||
|
|
|
|||
|
|
def to_string(self):
|
|||
|
|
where_str = match_where_str_begin_reg.sub('', self.__WHERE_STR)
|
|||
|
|
|
|||
|
|
if len(where_str) == 0:
|
|||
|
|
return '1'
|
|||
|
|
|
|||
|
|
return where_str
|
|||
|
|
|
|||
|
|
def get_bind_params(self):
|
|||
|
|
return self.__BIND_PARAMS
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取where条件表达式和绑定参数
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return (where条件<string>, 绑定参数<tuple>)
|
|||
|
|
'''
|
|||
|
|
where_str = match_where_str_begin_reg.sub('', self.__WHERE_STR)
|
|||
|
|
|
|||
|
|
if len(where_str) == 0:
|
|||
|
|
return '', ()
|
|||
|
|
|
|||
|
|
return ' WHERE {}'.format(where_str), self.__BIND_PARAMS
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__WHERE_STR = '1'
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Limit:
|
|||
|
|
'''
|
|||
|
|
@name limit条件收集类
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__LIMIT', '__SKIP']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__LIMIT = None
|
|||
|
|
self.__SKIP = None
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def set_limit(self, limit):
|
|||
|
|
'''
|
|||
|
|
@name 设置limit
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param limit<integer> 查询行数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__LIMIT = int(limit)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_skip(self, skip):
|
|||
|
|
'''
|
|||
|
|
@name 设置skip
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param skip<integer> 跳过的行数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__SKIP = int(skip)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取limit条件表达式
|
|||
|
|
@author Zhj<2022-17-16>
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
if self.__LIMIT is None:
|
|||
|
|
return ''
|
|||
|
|
|
|||
|
|
if self.__SKIP is None:
|
|||
|
|
return ' LIMIT {}'.format(str(self.__LIMIT))
|
|||
|
|
|
|||
|
|
return ' LIMIT {},{}'.format(str(self.__SKIP), str(self.__LIMIT))
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__LIMIT = None
|
|||
|
|
self.__SKIP = None
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Order:
|
|||
|
|
'''
|
|||
|
|
@name order排序条件收集类
|
|||
|
|
@author ZHj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__ORDERS', '__BIND_PARAMS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__ORDERS = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def add_order(self, field, ordering='ASC', params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加排序条件
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param ordering<string> 排序方式 ASC|DESC
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__ORDERS.append('{} {}'.format(
|
|||
|
|
_add_backtick_for_field(field),
|
|||
|
|
ordering.upper()
|
|||
|
|
))
|
|||
|
|
self.__BIND_PARAMS += _to_tuple(params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取排序条件表达式和绑定参数
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return (排序条件表达式<string>, 绑定参数<tuple>)
|
|||
|
|
'''
|
|||
|
|
if len(self.__ORDERS) == 0:
|
|||
|
|
return '', ()
|
|||
|
|
|
|||
|
|
return ' ORDER BY {}'.format(', '.join(self.__ORDERS)), self.__BIND_PARAMS
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__ORDERS = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Field:
|
|||
|
|
'''
|
|||
|
|
@name 查询字段收集类
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__FIELDS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__FIELDS = []
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def set_fields(self, *fields):
|
|||
|
|
'''
|
|||
|
|
@name 设置查询字段
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param fields<tuple> 查询字段列表
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__FIELDS = list(
|
|||
|
|
map(lambda field: _add_backtick_for_field(field),
|
|||
|
|
filter(lambda x: x is not None, fields)
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def add_fields(self, *fields):
|
|||
|
|
'''
|
|||
|
|
@name 添加查询字段
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param fields<tuple> 查询字段列表
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__FIELDS += list(
|
|||
|
|
map(lambda field: _add_backtick_for_field(field),
|
|||
|
|
filter(lambda x: x is not None, fields)
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取查询字段列表
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
if len(self.__FIELDS) == 0:
|
|||
|
|
return '*'
|
|||
|
|
|
|||
|
|
return ', '.join(list(set(self.__FIELDS)))
|
|||
|
|
|
|||
|
|
def is_empty(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查是否为空
|
|||
|
|
@author Zhj<2022-07-20>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return len(self.__FIELDS) == 0
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__FIELDS = []
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Group:
|
|||
|
|
'''
|
|||
|
|
@name 分组条件收集类
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__GROUPS', '__BIND_PARAMS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__GROUPS = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def add_group(self, condition, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加分组条件
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param condition<string> 分组条件
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__GROUPS.append(str(condition).strip())
|
|||
|
|
self.__BIND_PARAMS += _to_tuple(params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取分组条件表达式和绑定参数
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return (分组条件表达式<string>, 绑定参数<tuple>)
|
|||
|
|
'''
|
|||
|
|
if len(self.__GROUPS) == 0:
|
|||
|
|
return '', ()
|
|||
|
|
|
|||
|
|
return ' GROUP BY {}'.format(', '.join(self.__GROUPS)), self.__BIND_PARAMS
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__GROUPS = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Having:
|
|||
|
|
'''
|
|||
|
|
@name 分组筛选条件收集类
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__HAVINGS', '__BIND_PARAMS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__HAVINGS = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def add_having(self, condition, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加分组筛选条件
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param condition<string> 分组筛选条件
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__HAVINGS.append(str(condition).strip())
|
|||
|
|
self.__BIND_PARAMS += _to_tuple(params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取分组筛选条件表达式和绑定参数
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return (分组筛选条件表达式<string>, 绑定参数<tuple>)
|
|||
|
|
'''
|
|||
|
|
if len(self.__HAVINGS) == 0:
|
|||
|
|
return '', ()
|
|||
|
|
|
|||
|
|
return ' HAVING {}'.format(', '.join(self.__HAVINGS)), self.__BIND_PARAMS
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__HAVINGS = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Join:
|
|||
|
|
'''
|
|||
|
|
@name 关联条件收集类
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__JOINS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__JOINS = []
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def add_join(self, expression, condition, join_type='INNER', table_prefix=''):
|
|||
|
|
'''
|
|||
|
|
@name 添加关联条件
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param expression<string> 表达式
|
|||
|
|
@param condition<string> 关联条件
|
|||
|
|
@param join_type<string> 关联方式 INNER|LEFT|RIGHT
|
|||
|
|
@param table_prefix<string> 表前缀
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
m = match_table_name_reg.match(expression)
|
|||
|
|
|
|||
|
|
if m:
|
|||
|
|
expression = '{}{}'.format(
|
|||
|
|
_add_backtick_for_field('{}{}'.format(table_prefix, m.group(1).strip('`'))),
|
|||
|
|
'' if m.group(2) is None else ' AS {}'.format(_add_backtick_for_field(m.group(2)))
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.__JOINS.append('{} JOIN {} ON {}'.format(
|
|||
|
|
join_type.upper(),
|
|||
|
|
str(expression).strip(),
|
|||
|
|
str(condition).strip()
|
|||
|
|
))
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取关联条件表达式
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
if len(self.__JOINS) == 0:
|
|||
|
|
return ''
|
|||
|
|
|
|||
|
|
return ' ' + ' '.join(self.__JOINS)
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__JOINS = []
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Update:
|
|||
|
|
'''
|
|||
|
|
@name Update条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__UPDATES', '__BIND_PARAMS']
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.__UPDATES = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
|
|||
|
|
def add(self, field, value):
|
|||
|
|
'''
|
|||
|
|
@name 添加更新条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param value<integer|string> 值
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__UPDATES.append('{} = ?'.format(_add_backtick_for_field(field)))
|
|||
|
|
self.__BIND_PARAMS += _to_tuple(value)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def increment(self, field, step=1):
|
|||
|
|
'''
|
|||
|
|
@name 自增
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param step<integer> 值
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__UPDATES.append('{field} = {field} + {step}'.format(
|
|||
|
|
field=_add_backtick_for_field(field),
|
|||
|
|
step=str(int(step))
|
|||
|
|
))
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def decrement(self, field, step=1):
|
|||
|
|
'''
|
|||
|
|
@name 自减
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param step<integer> 值
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__UPDATES.append('{field} = {field} - {step}'.format(
|
|||
|
|
field=_add_backtick_for_field(field),
|
|||
|
|
step=str(int(step))
|
|||
|
|
))
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def exp(self, field, exp):
|
|||
|
|
'''
|
|||
|
|
@name 添加原生表达式
|
|||
|
|
@author Zhj<2022-12-08>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param exp<string> 原生表达式
|
|||
|
|
@return:
|
|||
|
|
'''
|
|||
|
|
self.__UPDATES.append('{field} = {exp}'.format(
|
|||
|
|
field=_add_backtick_for_field(field),
|
|||
|
|
exp=str(exp)
|
|||
|
|
))
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取更新表达式和绑定参数
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if self.is_empty():
|
|||
|
|
return '', ()
|
|||
|
|
|
|||
|
|
return ', '.join(self.__UPDATES), self.__BIND_PARAMS
|
|||
|
|
|
|||
|
|
def is_empty(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查update条件是否为空
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return len(self.__UPDATES) == 0
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__UPDATES = []
|
|||
|
|
self.__BIND_PARAMS = ()
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Duplicate(Update):
|
|||
|
|
'''
|
|||
|
|
@name Duplicate条件
|
|||
|
|
@author Zhj<2024-12-19>
|
|||
|
|
'''
|
|||
|
|
def __init__(self):
|
|||
|
|
Update.__init__(self)
|
|||
|
|
|
|||
|
|
def build(self):
|
|||
|
|
if self.is_empty():
|
|||
|
|
return '', ()
|
|||
|
|
|
|||
|
|
raw_sql, binds = Update.build(self)
|
|||
|
|
|
|||
|
|
return ' ON CONFLICT DO UPDATE SET ' + raw_sql, binds
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AlterTable:
|
|||
|
|
__slots__ = ['__weakref__', '__QUERY', '__COLUMNS', '__RENAME_TABLE', '__ALTERS']
|
|||
|
|
|
|||
|
|
def __init__(self, query):
|
|||
|
|
if not isinstance(query, SqliteEasy):
|
|||
|
|
raise RuntimeError('参数query必须是一个SqliteEasy类型')
|
|||
|
|
self.__QUERY = weakref.proxy(query)
|
|||
|
|
self.__COLUMNS = []
|
|||
|
|
self.__RENAME_TABLE = None
|
|||
|
|
self.__ALTERS = []
|
|||
|
|
|
|||
|
|
# def __del__(self):
|
|||
|
|
# self.clear()
|
|||
|
|
# self.__COLUMNS = []
|
|||
|
|
# self.__ALTERS = []
|
|||
|
|
# self.__QUERY = None
|
|||
|
|
|
|||
|
|
def rename_table(self, new_table_name):
|
|||
|
|
'''
|
|||
|
|
@name 更新表名
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param new_table_name<string> 新表名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__RENAME_TABLE = ' RENAME TO {}'.format(_add_backtick_for_field(new_table_name))
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def rename_column(self, col_name, new_col_name):
|
|||
|
|
'''
|
|||
|
|
@name 更新字段名
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 当前字段名
|
|||
|
|
@param new_col_name<string> 新字段名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if self.__column_exists(col_name):
|
|||
|
|
self.__ALTERS.append(' RENAME COLUMN {} TO {}'.format(_add_backtick_for_field(col_name), _add_backtick_for_field(new_col_name)))
|
|||
|
|
self.__COLUMNS.remove(col_name)
|
|||
|
|
self.__COLUMNS.append(new_col_name)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def add_column(self, col_name, prop, force=False):
|
|||
|
|
'''
|
|||
|
|
@name 新增字段
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 字段名
|
|||
|
|
@param prop<string> 字段属性
|
|||
|
|
@param force<?bool> 是否强制新增(删除旧的字段)[可选]
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if force:
|
|||
|
|
self.drop_column(col_name)
|
|||
|
|
|
|||
|
|
if not self.__column_exists(col_name):
|
|||
|
|
self.__ALTERS.append(' ADD COLUMN {} {}'.format(_add_backtick_for_field(col_name), prop))
|
|||
|
|
self.__COLUMNS.append(col_name)
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def drop_column(self, col_name):
|
|||
|
|
'''
|
|||
|
|
@name 删除字段
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 字段名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if self.__column_exists(col_name):
|
|||
|
|
self.__ALTERS.append(' DROP COLUMN {}'.format(_add_backtick_for_field(col_name)))
|
|||
|
|
self.__COLUMNS.remove(col_name)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def __column_exists(self, col_name):
|
|||
|
|
'''
|
|||
|
|
@name 检查字段是否已经存在
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 字段名
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if len(self.__COLUMNS) == 0:
|
|||
|
|
self.__COLUMNS = self.__QUERY.get_columns()
|
|||
|
|
|
|||
|
|
return col_name in self.__COLUMNS
|
|||
|
|
|
|||
|
|
def build(self, table_name):
|
|||
|
|
'''
|
|||
|
|
@name 构建语句
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param table_name<string> 表名
|
|||
|
|
@return string|None
|
|||
|
|
'''
|
|||
|
|
if self.is_empty():
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
ret = "begin;\n"
|
|||
|
|
ret += "\n".join(list(map(lambda x: 'ALTER TABLE {}{};'.format(table_name, x), self.__ALTERS)))
|
|||
|
|
ret += "ALTER TABLE {}{};\n".format(table_name,
|
|||
|
|
self.__RENAME_TABLE) if self.__RENAME_TABLE is not None else "\n"
|
|||
|
|
ret += 'commit;'
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def is_empty(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查更新条件是否为空
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return self.__RENAME_TABLE is None and len(self.__ALTERS) == 0
|
|||
|
|
|
|||
|
|
def clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空收集数据
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__ALTERS = []
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DbConnection:
|
|||
|
|
'''
|
|||
|
|
@name Sqlite数据库连接类(相比Db类更加底层)
|
|||
|
|
@author Zhj<2022-12-13>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__DB_NAME', '__DB_PATH', '__DB_LOCK_FILE', '__CONN', '__DEBUG_LOG', 'ENGINE']
|
|||
|
|
|
|||
|
|
def __init__(self, db_name, engine=None):
|
|||
|
|
'''
|
|||
|
|
@name 初始化函数
|
|||
|
|
@author Zhj<2022-12-14>
|
|||
|
|
@param db_name<string> 数据库名称(全路径 不包含.db)
|
|||
|
|
@return void
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
self.ENGINE = sqlite3 if engine is None else engine
|
|||
|
|
self.__DB_NAME = db_name
|
|||
|
|
self.__DB_PATH = '{}.db'.format(db_name)
|
|||
|
|
|
|||
|
|
if str(db_name).startswith(':memory:'):
|
|||
|
|
self.__DB_PATH = ':memory:'
|
|||
|
|
else:
|
|||
|
|
if os.path.exists(db_name):
|
|||
|
|
self.__DB_PATH = db_name
|
|||
|
|
|
|||
|
|
# 数据库连接对象
|
|||
|
|
self.__CONN: typing.Optional[sqlite3.Connection] = None
|
|||
|
|
|
|||
|
|
# 数据库并发文件锁
|
|||
|
|
self.__DB_LOCK_FILE = None
|
|||
|
|
if self.__DB_PATH != ':memory:':
|
|||
|
|
self.__DB_LOCK_FILE = '/tmp/aap_locks/{}.lock'.format(self.__DB_PATH.replace('/', '____'))
|
|||
|
|
dirname = os.path.dirname(self.__DB_LOCK_FILE)
|
|||
|
|
if not os.path.exists(dirname):
|
|||
|
|
os.makedirs(dirname, 0o775)
|
|||
|
|
if not os.path.exists(self.__DB_LOCK_FILE):
|
|||
|
|
with open(self.__DB_LOCK_FILE, 'ab'):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 连接数据库
|
|||
|
|
self.connect()
|
|||
|
|
|
|||
|
|
# # 析构函数
|
|||
|
|
# def __del__(self):
|
|||
|
|
# try:
|
|||
|
|
# # 关闭数据库连接
|
|||
|
|
# self.close()
|
|||
|
|
# except BaseException as e:
|
|||
|
|
# _log(e)
|
|||
|
|
|
|||
|
|
# 数据库读操作并发锁
|
|||
|
|
@contextmanager
|
|||
|
|
def __rlock(self):
|
|||
|
|
if self.__DB_LOCK_FILE is not None:
|
|||
|
|
with open(self.__DB_LOCK_FILE, 'rb') as fp:
|
|||
|
|
fcntl.flock(fp.fileno(), fcntl.LOCK_SH)
|
|||
|
|
yield
|
|||
|
|
else:
|
|||
|
|
yield
|
|||
|
|
|
|||
|
|
# 数据库写操作并发锁
|
|||
|
|
@contextmanager
|
|||
|
|
def __wlock(self):
|
|||
|
|
if self.__DB_LOCK_FILE is not None:
|
|||
|
|
with open(self.__DB_LOCK_FILE, 'rb+') as fp:
|
|||
|
|
fcntl.flock(fp.fileno(), fcntl.LOCK_EX)
|
|||
|
|
yield
|
|||
|
|
else:
|
|||
|
|
yield
|
|||
|
|
|
|||
|
|
# 获取sqlite连接对象
|
|||
|
|
def conn_obj(self) -> sqlite3.Connection:
|
|||
|
|
'''
|
|||
|
|
@name 获取sqlite数据库连接对象<sqlite3.Connection>
|
|||
|
|
@author Zhj<2022-12-22>
|
|||
|
|
@return sqlite3.Connection|None
|
|||
|
|
'''
|
|||
|
|
return self.__CONN
|
|||
|
|
|
|||
|
|
# 连接sqlite
|
|||
|
|
def connect(self):
|
|||
|
|
if isinstance(self.__CONN, self.ENGINE.Connection):
|
|||
|
|
return self.__CONN
|
|||
|
|
|
|||
|
|
# 连接数据库(写)
|
|||
|
|
self.__CONN = self.ENGINE.connect(self.__DB_PATH, timeout=15, check_same_thread=False)
|
|||
|
|
self.__CONN.text_factory = str
|
|||
|
|
self.__CONN.isolation_level = 'IMMEDIATE'
|
|||
|
|
|
|||
|
|
# 关闭连接
|
|||
|
|
def close(self):
|
|||
|
|
'''
|
|||
|
|
@name 关闭sqlite连接
|
|||
|
|
'''
|
|||
|
|
# 关闭sqlite数据库连接
|
|||
|
|
if isinstance(self.__CONN, self.ENGINE.Connection):
|
|||
|
|
try:
|
|||
|
|
self.__CONN.close()
|
|||
|
|
except BaseException as e:
|
|||
|
|
_log(e)
|
|||
|
|
|
|||
|
|
# 开启事务(sqlite自动开启,无需手动调用)
|
|||
|
|
def start_transaction(self):
|
|||
|
|
'''
|
|||
|
|
@name 开启事务
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 提交事务
|
|||
|
|
def commit(self) -> bool:
|
|||
|
|
'''
|
|||
|
|
@name 提交事务
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if isinstance(self.__CONN, self.ENGINE.Connection) and self.__CONN.in_transaction:
|
|||
|
|
self.__CONN.commit()
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 回滚事务
|
|||
|
|
def rollback(self) -> bool:
|
|||
|
|
'''
|
|||
|
|
@name 回滚事务
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if isinstance(self.__CONN, self.ENGINE.Connection) and self.__CONN.in_transaction:
|
|||
|
|
self.__CONN.rollback()
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 重试辅助函数
|
|||
|
|
def __sqlite_retry_help(self, fn, *args, **kwargs):
|
|||
|
|
retries = 25 # 尝试最大次数
|
|||
|
|
retry_interval = 20 # 每次尝试间隔时间/ms
|
|||
|
|
|
|||
|
|
while retries > 0:
|
|||
|
|
try:
|
|||
|
|
return fn(*args, **kwargs)
|
|||
|
|
except (
|
|||
|
|
SystemError, KeyError, self.ENGINE.InterfaceError, self.ENGINE.InternalError, self.ENGINE.OperationalError) as e:
|
|||
|
|
# 数据库操作错误,不是锁协议错误,直接抛出异常
|
|||
|
|
if isinstance(e, self.ENGINE.OperationalError) and str(e) not in ['locking protocol',
|
|||
|
|
'database is locked']:
|
|||
|
|
raise e
|
|||
|
|
|
|||
|
|
# 最后一次尝试失败后直接raise异常
|
|||
|
|
if retries == 1:
|
|||
|
|
raise e
|
|||
|
|
|
|||
|
|
# 打印异常信息
|
|||
|
|
_log(e)
|
|||
|
|
e = None
|
|||
|
|
del (e,)
|
|||
|
|
|
|||
|
|
time.sleep(retry_interval * 0.001)
|
|||
|
|
finally:
|
|||
|
|
retries -= 1
|
|||
|
|
|
|||
|
|
# 查询
|
|||
|
|
def query(self, sql, params=(), take_first=False):
|
|||
|
|
'''
|
|||
|
|
@name 执行查询SQL
|
|||
|
|
@param sql<string> sql语句
|
|||
|
|
@param params<list|tuple> 绑定参数[可选]
|
|||
|
|
@param take_first<bool> 是否只获取一行数据[可选 默认获取所有行]
|
|||
|
|
@return list|dict|None
|
|||
|
|
'''
|
|||
|
|
# 读锁
|
|||
|
|
with self.__rlock():
|
|||
|
|
s_time = time.time()
|
|||
|
|
e_time = s_time
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
ret = self.__sqlite_retry_help(self.__query_help, sql, params, take_first)
|
|||
|
|
e_time = time.time()
|
|||
|
|
return ret
|
|||
|
|
finally:
|
|||
|
|
if e_time - s_time > 1:
|
|||
|
|
_log2('{}s {}'.format(round(e_time - s_time, 2),
|
|||
|
|
reduce(lambda x, y: search_question_reg.sub(
|
|||
|
|
str(y) if is_number(y) else "'%s'" % y, x, 1), params, sql)),
|
|||
|
|
'sqlite_slow.log')
|
|||
|
|
|
|||
|
|
# 查询(辅助函数)
|
|||
|
|
def __query_help(self, sql, params=(), take_first=False):
|
|||
|
|
with _auto_repair_context(self.__DB_PATH):
|
|||
|
|
try:
|
|||
|
|
# 执行SQL
|
|||
|
|
# 获取游标对象
|
|||
|
|
cur = self.__CONN.execute(sql, _to_tuple(params))
|
|||
|
|
except:
|
|||
|
|
# 打印sql语句与绑定参数
|
|||
|
|
_log('{}\nbindings: {}\ndb: {}'.format(sql, params, self.__DB_NAME))
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
# 抛出异常
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
ret = _format_cursor(cur)
|
|||
|
|
|
|||
|
|
e_time = time.time()
|
|||
|
|
|
|||
|
|
if e_time - s_time > 1:
|
|||
|
|
_log2('{}s format_cursor'.format(round(e_time - s_time, 2)), 'sqlite_slow.log')
|
|||
|
|
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
# 只获取首行数据
|
|||
|
|
if take_first:
|
|||
|
|
if len(ret) == 0:
|
|||
|
|
return None
|
|||
|
|
return ret[0]
|
|||
|
|
|
|||
|
|
return list(ret)
|
|||
|
|
except:
|
|||
|
|
# 打印sql语句与绑定参数
|
|||
|
|
_log('{}\nbindings: {}\ndb: {}'.format(sql, params, self.__DB_NAME))
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
# 执行单条SQL语句
|
|||
|
|
def execute(self, sql, params=(), get_rowid=False):
|
|||
|
|
'''
|
|||
|
|
@name 执行SQL
|
|||
|
|
@param sql<string> sql语句
|
|||
|
|
@param params<tuple|list> 绑定参数
|
|||
|
|
@param get_rowid<bool> 获取插入ID
|
|||
|
|
@return int 影响行数或插入ID
|
|||
|
|
'''
|
|||
|
|
# 写锁
|
|||
|
|
with self.__wlock():
|
|||
|
|
return self.__sqlite_retry_help(self.__execute_help, sql, params, get_rowid)
|
|||
|
|
|
|||
|
|
# 执行单条SQL语句(辅助函数)
|
|||
|
|
def __execute_help(self, sql, params=(), get_rowid=False):
|
|||
|
|
with _auto_repair_context(self.__DB_PATH):
|
|||
|
|
try:
|
|||
|
|
# 获取游标对象
|
|||
|
|
cur = self.__CONN.cursor()
|
|||
|
|
|
|||
|
|
# 执行SQL
|
|||
|
|
cur.execute(sql, _to_tuple(params))
|
|||
|
|
except:
|
|||
|
|
# 打印sql语句与绑定参数
|
|||
|
|
_log('{}\nbindings: {}\ndb: {}'.format(sql, params, self.__DB_NAME))
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
# 抛出异常
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 获取新插入数据ID
|
|||
|
|
if get_rowid:
|
|||
|
|
return cur.lastrowid
|
|||
|
|
|
|||
|
|
# 返回受影响的行数
|
|||
|
|
return cur.rowcount
|
|||
|
|
finally:
|
|||
|
|
# 主动关闭Cursor
|
|||
|
|
cur.close()
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
# 执行批量写入
|
|||
|
|
def execute_many(self, sql, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 批量插入
|
|||
|
|
@param sql<string> sql语句
|
|||
|
|
@param params<tuple|list> 绑定参数
|
|||
|
|
@return int 影响行数
|
|||
|
|
'''
|
|||
|
|
# 写锁
|
|||
|
|
with self.__wlock():
|
|||
|
|
return self.__sqlite_retry_help(self.__execute_many_help, sql, params)
|
|||
|
|
|
|||
|
|
# 执行批量写入(辅助函数)
|
|||
|
|
def __execute_many_help(self, sql, params=()):
|
|||
|
|
with _auto_repair_context(self.__DB_PATH):
|
|||
|
|
try:
|
|||
|
|
# 获取游标对象
|
|||
|
|
cur = self.__CONN.cursor()
|
|||
|
|
|
|||
|
|
# 执行SQL
|
|||
|
|
cur.executemany(sql, params)
|
|||
|
|
except:
|
|||
|
|
# 打印sql语句与绑定参数
|
|||
|
|
_log('{}\nbindings: {}\ndb: {}'.format(sql, params, self.__DB_NAME))
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
# 抛出异常
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
rowcount = cur.rowcount
|
|||
|
|
|
|||
|
|
# 主动关闭Cursor
|
|||
|
|
cur.close()
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
return rowcount
|
|||
|
|
|
|||
|
|
# 执行多条SQL语句
|
|||
|
|
def execute_script(self, sql):
|
|||
|
|
'''
|
|||
|
|
@name 批量执行SQL
|
|||
|
|
@param sql<string> sql语句集合
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
# 写锁
|
|||
|
|
with self.__wlock():
|
|||
|
|
return self.__sqlite_retry_help(self.__execute_script_help, sql)
|
|||
|
|
|
|||
|
|
# 执行多条SQL语句(辅助函数)
|
|||
|
|
def __execute_script_help(self, sql):
|
|||
|
|
with _auto_repair_context(self.__DB_PATH):
|
|||
|
|
try:
|
|||
|
|
# 获取游标对象
|
|||
|
|
cur = self.__CONN.cursor()
|
|||
|
|
|
|||
|
|
# 执行SQL
|
|||
|
|
cur.executescript(sql)
|
|||
|
|
except:
|
|||
|
|
# 打印sql语句与绑定参数
|
|||
|
|
_log('{}\ndb: {}'.format(sql, self.__DB_NAME))
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
# 抛出异常
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
# 主动关闭Cursor
|
|||
|
|
cur.close()
|
|||
|
|
|
|||
|
|
cur = None
|
|||
|
|
del (cur,)
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 执行vacuum整理数据库空间
|
|||
|
|
def vacuum(self):
|
|||
|
|
'''
|
|||
|
|
@name 执行vaccum整理数据库空间
|
|||
|
|
@author Zhj<2022-12-22>
|
|||
|
|
@return int
|
|||
|
|
'''
|
|||
|
|
# 写锁
|
|||
|
|
with self.__wlock():
|
|||
|
|
return self.execute('vacuum')
|
|||
|
|
|
|||
|
|
# 备份数据库
|
|||
|
|
def backup(self, dest_conn) -> bool:
|
|||
|
|
'''
|
|||
|
|
@name 将当前数据库备份到目标数据库
|
|||
|
|
@author Zhj<2022-12-22>
|
|||
|
|
@param dest_conn<DbConnection> 目标数据库连接对象
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if dest_conn.conn_obj() is None:
|
|||
|
|
raise PanelError('dest_conn not connect to sqlite.')
|
|||
|
|
|
|||
|
|
# 写锁
|
|||
|
|
with self.__wlock():
|
|||
|
|
with _auto_repair_context(self.__DB_PATH):
|
|||
|
|
# 开始备份(全量备份)
|
|||
|
|
self.__CONN.backup(dest_conn.conn_obj())
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 导出数据库
|
|||
|
|
def dump(self, dest_file: str, row_check_func: typing.Optional[callable] = None) -> bool:
|
|||
|
|
"""
|
|||
|
|
@name 导出数据库
|
|||
|
|
@author Zhj<2024-08-08>
|
|||
|
|
@param dest_file<str> 目标文件
|
|||
|
|
@param row_check_func<?callable> 语句行检查函数 row_check_func(row: str) -> bool
|
|||
|
|
@return bool
|
|||
|
|
"""
|
|||
|
|
# 写锁
|
|||
|
|
with self.__wlock():
|
|||
|
|
with open(dest_file, 'w') as fp:
|
|||
|
|
for row in self.__CONN.iterdump():
|
|||
|
|
# 当传入了语句行检查函数时,执行检查函数
|
|||
|
|
if row_check_func is not None and not row_check_func(row):
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 导出数据行
|
|||
|
|
fp.write(row + '\n')
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Snapshot:
|
|||
|
|
__slots__ = ['pk', 'from_sub_query', 'table', 'prefix', 'alias', 'where', 'limit', 'order',
|
|||
|
|
'field', 'group', 'having', 'join', 'update', 'duplicate']
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Db:
|
|||
|
|
'''
|
|||
|
|
@name Sqlite数据库连接类
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__DB_NAME', '__DB_CONN', '__AUTO_COMMIT', '__AUTO_VACUUM', '__NEED_VACUUM', '__DEBUG_LOG', '__QUERIES', 'ENGINE']
|
|||
|
|
|
|||
|
|
__DB_ROOT_DIR = '{}/data/'.format(_BASE_DIR)
|
|||
|
|
|
|||
|
|
def __init__(self, db_name, engine=None, auto_connect: bool = True):
|
|||
|
|
'''
|
|||
|
|
@name 初始化函数
|
|||
|
|
@author Zhj<2022-12-14>
|
|||
|
|
@param db_name<string> 数据库名称
|
|||
|
|
@param brandnew<bool> 是否开启一个全新连接
|
|||
|
|
@return void
|
|||
|
|
'''
|
|||
|
|
self.ENGINE = sqlite3 if engine is None else engine
|
|||
|
|
if str(db_name).startswith(':memory:'):
|
|||
|
|
self.__DB_NAME = db_name # 内存数据库
|
|||
|
|
else:
|
|||
|
|
self.__DB_NAME = os.path.join(self.__DB_ROOT_DIR, re.sub(r'\.db$', '', db_name)) # 数据库名称(全路径 不带.db)
|
|||
|
|
self.__DB_CONN: typing.Optional[DbConnection] = None # 数据库连接对象
|
|||
|
|
self.__AUTO_COMMIT = True # 是否自动提交事务(默认自动提交)
|
|||
|
|
self.__AUTO_VACUUM = False # 是否自动释放空间
|
|||
|
|
self.__NEED_VACUUM = False # 事务提交后是否需要释放空间
|
|||
|
|
self.__DEBUG_LOG = False # 是否开启调试日志
|
|||
|
|
self.__QUERIES = [] # 查询构造器数量
|
|||
|
|
|
|||
|
|
if auto_connect:
|
|||
|
|
self._connect()
|
|||
|
|
|
|||
|
|
def __del__(self):
|
|||
|
|
self.close()
|
|||
|
|
del (self.__DB_CONN,)
|
|||
|
|
|
|||
|
|
def __enter__(self):
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def __exit__(self, exc_type, exc_value, exc_trackback):
|
|||
|
|
self.close()
|
|||
|
|
|
|||
|
|
def _connect(self):
|
|||
|
|
'''
|
|||
|
|
@name 连接Sqlite数据库
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__DB_CONN = DbConnection(db_name=self.__DB_NAME, engine=self.ENGINE)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
'''
|
|||
|
|
@name 将数据库连接返回连接池
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
try:
|
|||
|
|
# 释放数据库连接
|
|||
|
|
if self.__DB_CONN is None:
|
|||
|
|
return
|
|||
|
|
self.__DB_CONN.close()
|
|||
|
|
self.__DB_CONN = None
|
|||
|
|
except BaseException as e:
|
|||
|
|
_log(e)
|
|||
|
|
|
|||
|
|
def client(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取数据库连接对象
|
|||
|
|
@return SqliteClient
|
|||
|
|
'''
|
|||
|
|
return self.__DB_CONN
|
|||
|
|
|
|||
|
|
def db_name(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取数据库名称
|
|||
|
|
@author Zhj<2022-09-07>
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
return self.__DB_NAME
|
|||
|
|
|
|||
|
|
def auto_vacuum(self, auto_vacuum=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置是否自动释放空间
|
|||
|
|
@author Zhj<2022-09-27>
|
|||
|
|
@param auto_vacuum<?bool> 是否自动释放空间
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__AUTO_VACUUM = auto_vacuum
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def need_vacuum(self, need_vacuum=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置是否需要清理空间
|
|||
|
|
@author Zhj<2022-09-02>
|
|||
|
|
@param need_vacuum<?bool> 是否需要清理空间
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__NEED_VACUUM = need_vacuum
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def autocommit(self, autocommit=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置自动提交事务状态
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param autocommit<bool> 是否自动提交事务
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__AUTO_COMMIT = autocommit
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def is_autocommit(self):
|
|||
|
|
'''
|
|||
|
|
@name 是否自动提交事务
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return self.__AUTO_COMMIT
|
|||
|
|
|
|||
|
|
def is_autovacuum(self):
|
|||
|
|
'''
|
|||
|
|
@name 是否自动释放空间
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return self.__AUTO_VACUUM
|
|||
|
|
|
|||
|
|
def commit(self):
|
|||
|
|
'''
|
|||
|
|
@name 提交事务
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB_CONN is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 记录commit语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 提交事务
|
|||
|
|
ret = self.__DB_CONN.commit()
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.is_debug():
|
|||
|
|
self.debug_log('commit', self.db_name(), time.time() - s_time)
|
|||
|
|
|
|||
|
|
# 开启了自动释放空间时
|
|||
|
|
# 释放空间
|
|||
|
|
if self.__NEED_VACUUM and self.is_autovacuum():
|
|||
|
|
self.__NEED_VACUUM = False
|
|||
|
|
self.vacuum()
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def rollback(self):
|
|||
|
|
'''
|
|||
|
|
@name 回滚事务
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB_CONN is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
self.__NEED_VACUUM = False
|
|||
|
|
|
|||
|
|
# 记录rollback语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
ret = self.__DB_CONN.rollback()
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.is_debug():
|
|||
|
|
self.debug_log('rollback', self.db_name(), time.time() - s_time)
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def query(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取查询构造器对象
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@return SqliteEasy
|
|||
|
|
'''
|
|||
|
|
self.__QUERIES.append(None)
|
|||
|
|
return SqliteEasy(self)
|
|||
|
|
|
|||
|
|
def query_done(self):
|
|||
|
|
'''
|
|||
|
|
@name 标记查询构造器已关闭
|
|||
|
|
@author Zhj<2023-02-08>
|
|||
|
|
@return None
|
|||
|
|
'''
|
|||
|
|
del (self.__QUERIES[-1:],)
|
|||
|
|
|
|||
|
|
def queries(self):
|
|||
|
|
'''
|
|||
|
|
@name 查看当前查询构造器数量
|
|||
|
|
@author Zhj<2023-02-08>
|
|||
|
|
@return int
|
|||
|
|
'''
|
|||
|
|
return len(self.__QUERIES)
|
|||
|
|
|
|||
|
|
def vacuum(self):
|
|||
|
|
'''
|
|||
|
|
@name 释放空间
|
|||
|
|
@author Zhj<2022-12-22>
|
|||
|
|
@return int
|
|||
|
|
'''
|
|||
|
|
# 记录vacuum语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 执行vacuum
|
|||
|
|
ret = self.__DB_CONN.vacuum()
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.is_debug():
|
|||
|
|
self.debug_log('vacuum', self.db_name(), time.time() - s_time)
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def backup(self, dest_db):
|
|||
|
|
'''
|
|||
|
|
@name 备份数据库(>=py3.7)
|
|||
|
|
@author Zhj<2022-12-22>
|
|||
|
|
@param dest_db<Db> 目标数据库连接对象
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if not isinstance(dest_db, Db):
|
|||
|
|
raise PanelError('dest_db must a Db object.')
|
|||
|
|
|
|||
|
|
# 记录执行备份开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 开始备份
|
|||
|
|
ret = self.__DB_CONN.backup(dest_db.client())
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.is_debug():
|
|||
|
|
self.debug_log('backup to {}'.format(dest_db.db_name()), self.db_name(), time.time() - s_time)
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def dump(self, dest_file: str, row_check_func: typing.Optional[callable] = None) -> bool:
|
|||
|
|
'''
|
|||
|
|
@name 导出数据库(>=py3.7)
|
|||
|
|
@author Zhj<2024-08-08>
|
|||
|
|
@param dest_file<str> 目标文件
|
|||
|
|
@param row_check_func<?callable> 语句行检查函数 row_check_func(row: str) -> bool
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
# 记录执行导出开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 开始导出
|
|||
|
|
ret = self.__DB_CONN.dump(dest_file, row_check_func)
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.is_debug():
|
|||
|
|
self.debug_log('dump to {}'.format(dest_file), self.db_name(), time.time() - s_time)
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def debug(self, debug_state=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置调试
|
|||
|
|
@param debug_state<bool> 调试状态
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__DEBUG_LOG = debug_state
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def is_debug(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查当前是否开启了调试日志
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
return self.__DEBUG_LOG
|
|||
|
|
|
|||
|
|
def debug_log(self, content, db_name, cost_time):
|
|||
|
|
'''
|
|||
|
|
@name 写查询日志
|
|||
|
|
@author Zhj<2022-09-27>
|
|||
|
|
@param content<string> sql语句
|
|||
|
|
@param db_name<string> 数据库名称
|
|||
|
|
@param cost_time<float> 执行耗时/s
|
|||
|
|
@return void
|
|||
|
|
'''
|
|||
|
|
# 获取当前日期时间
|
|||
|
|
cur_datetime = time.strftime('%Y-%m-%d %X')
|
|||
|
|
|
|||
|
|
# sql查询日志目录
|
|||
|
|
log_dir = '{}/logs/sql_log/{}'.format(_BASE_DIR, time.strftime('%Y%m'))
|
|||
|
|
|
|||
|
|
# 目录不存在时创建
|
|||
|
|
if not os.path.exists(log_dir):
|
|||
|
|
os.makedirs(log_dir, 0o600)
|
|||
|
|
|
|||
|
|
with open('{}/{}.log'.format(log_dir, cur_datetime[8:10]), 'a') as fp:
|
|||
|
|
fp.write('[{}]{} {}ms {}\n'.format(cur_datetime, db_name, round(cost_time * 1000, 2), content))
|
|||
|
|
|
|||
|
|
def synchronous_off(self):
|
|||
|
|
'''
|
|||
|
|
@name 关闭同步
|
|||
|
|
@author Zhj<2023-02-17>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__DB_CONN.execute_script('PRAGMA synchronous=0;')
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def synchronous_off_wal(self):
|
|||
|
|
'''
|
|||
|
|
@name 设置WAL日志模式并关闭同步
|
|||
|
|
@author Zhj<2023-02-17>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__DB_CONN.execute_script('PRAGMA journal_mode=wal;\nPRAGMA synchronous=0;\nPRAGMA temp_store=memory;\nPRAGMA mmap_size=30000000000;')
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def integrity_check(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查数据库是否完整
|
|||
|
|
@author Zhj<2023-03-15>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
try:
|
|||
|
|
ret = self.__DB_CONN.query('PRAGMA integrity_check', take_first=True)
|
|||
|
|
return ret['integrity_check'] == 'ok'
|
|||
|
|
except:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
class SqliteEasy:
|
|||
|
|
'''
|
|||
|
|
@name Sqlite查询类
|
|||
|
|
@author Zhj<2022-07-15>
|
|||
|
|
'''
|
|||
|
|
__slots__ = ['__weakref__', '__DB', '__DB_TABLE', '__FETCH_SQL', '__EXPLAIN', '__PK', '__OPT_PREFIX', '__OPT_ALIAS',
|
|||
|
|
'__OPT_WHERE', '__OPT_LIMIT', '__OPT_ORDER', '__OPT_FIELD', '__OPT_GROUP', '__OPT_HAVING',
|
|||
|
|
'__OPT_JOIN', '__OPT_UPDATE', '__OPT_DUPLICATE', '__OPT_ALTER_TABLE', '__FROM_SUB_QUERY',
|
|||
|
|
'__CONFLICT_OPTIONS']
|
|||
|
|
|
|||
|
|
def __init__(self, db: typing.Optional[Db] = None):
|
|||
|
|
self.__DB = None # 数据库对象
|
|||
|
|
self.__DB_TABLE = None # 表名(包含前缀)
|
|||
|
|
self.__FETCH_SQL = False # 是否输出sql语句
|
|||
|
|
self.__EXPLAIN = False # 分析sql语句
|
|||
|
|
self.__PK = None # 主键字段名
|
|||
|
|
self.__OPT_PREFIX = 'bt_' # 表前缀
|
|||
|
|
self.__OPT_ALIAS = None # 表别名
|
|||
|
|
self.__OPT_WHERE = Where() # where条件
|
|||
|
|
self.__OPT_LIMIT = Limit() # limit条件
|
|||
|
|
self.__OPT_ORDER = Order() # order条件
|
|||
|
|
self.__OPT_FIELD = Field() # field条件
|
|||
|
|
self.__OPT_GROUP = Group() # group条件
|
|||
|
|
self.__OPT_HAVING = Having() # having条件
|
|||
|
|
self.__OPT_JOIN = Join() # 联表条件
|
|||
|
|
self.__OPT_UPDATE = Update() # update条件
|
|||
|
|
self.__OPT_DUPLICATE = Duplicate() # Duplicate
|
|||
|
|
self.__OPT_ALTER_TABLE = AlterTable(self) # 更新表结构条件
|
|||
|
|
self.__FROM_SUB_QUERY = False # 是否通过子查询
|
|||
|
|
self.__CONFLICT_OPTIONS = (
|
|||
|
|
'ROLLBACK', # 回滚
|
|||
|
|
'ABORT', # 撤销
|
|||
|
|
'FAIL', # 抛出失败异常
|
|||
|
|
'IGNORE', # 忽略
|
|||
|
|
'REPLACE', # 覆盖
|
|||
|
|
) # 发生约束冲突时,额外的处理选项
|
|||
|
|
|
|||
|
|
if db is not None:
|
|||
|
|
self.__DB = db
|
|||
|
|
|
|||
|
|
def __del__(self):
|
|||
|
|
self.close()
|
|||
|
|
|
|||
|
|
self.__OPT_WHERE = None
|
|||
|
|
self.__OPT_LIMIT = None
|
|||
|
|
self.__OPT_ORDER = None
|
|||
|
|
self.__OPT_FIELD = None
|
|||
|
|
self.__OPT_GROUP = None
|
|||
|
|
self.__OPT_HAVING = None
|
|||
|
|
self.__OPT_JOIN = None
|
|||
|
|
self.__OPT_UPDATE = None
|
|||
|
|
self.__OPT_ALTER_TABLE = None
|
|||
|
|
|
|||
|
|
del (self.__OPT_WHERE,
|
|||
|
|
self.__OPT_LIMIT,
|
|||
|
|
self.__OPT_ORDER,
|
|||
|
|
self.__OPT_FIELD,
|
|||
|
|
self.__OPT_GROUP,
|
|||
|
|
self.__OPT_HAVING,
|
|||
|
|
self.__OPT_JOIN,
|
|||
|
|
self.__OPT_UPDATE,
|
|||
|
|
self.__OPT_ALTER_TABLE,)
|
|||
|
|
|
|||
|
|
def __enter__(self):
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|||
|
|
self.close()
|
|||
|
|
|
|||
|
|
def set_db(self, db):
|
|||
|
|
'''
|
|||
|
|
@name 设置数据库对象
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@param db<Db> 数据库对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(db, Db):
|
|||
|
|
raise PanelError('db must a instance of core.include.sqlite_server.Db')
|
|||
|
|
|
|||
|
|
self.__DB = db
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def get_db(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取数据库对象
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@return Db|None
|
|||
|
|
'''
|
|||
|
|
return self.__DB
|
|||
|
|
|
|||
|
|
def set_pk(self, pk):
|
|||
|
|
'''
|
|||
|
|
@name 设置主键字段名
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param pk<string> 主键字段名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__PK = pk
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_where_obj(self, where_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Where对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param where_obj<Where> Where对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(where_obj, Where):
|
|||
|
|
raise PanelError('where_obj must a instance of core.include.sqlite_server.Where')
|
|||
|
|
self.__OPT_WHERE = where_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def get_where_obj(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取Where对象
|
|||
|
|
@return Where
|
|||
|
|
'''
|
|||
|
|
return self.__OPT_WHERE
|
|||
|
|
|
|||
|
|
def set_limit_obj(self, limit_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Limit对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param limit_obj<Limit> Limit对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(limit_obj, Limit):
|
|||
|
|
raise PanelError('limit_obj must a instance of core.include.sqlite_server.Limit')
|
|||
|
|
self.__OPT_LIMIT = limit_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_order_obj(self, order_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Order对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param order_obj<Order> Order对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(order_obj, Order):
|
|||
|
|
raise PanelError('order_obj must a instance of core.include.sqlite_server.Order')
|
|||
|
|
self.__OPT_ORDER = order_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_field_obj(self, field_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Field对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param field_obj<Field> Field对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(field_obj, Field):
|
|||
|
|
raise PanelError('field_obj must a instance of core.include.sqlite_server.Field')
|
|||
|
|
self.__OPT_FIELD = field_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_group_obj(self, group_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Group对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param group_obj<Group> Group对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(group_obj, Group):
|
|||
|
|
raise PanelError('group_obj must a instance of core.include.sqlite_server.Group')
|
|||
|
|
self.__OPT_GROUP = group_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_having_obj(self, having_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Having对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param having_obj<Having> Having对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(having_obj, Having):
|
|||
|
|
raise PanelError('having_obj must a instance of core.include.sqlite_server.Having')
|
|||
|
|
self.__OPT_HAVING = having_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_join_obj(self, join_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Join对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param join_obj<Join> Join对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(join_obj, Join):
|
|||
|
|
raise PanelError('join_obj must a instance of core.include.sqlite_server.Join')
|
|||
|
|
self.__OPT_JOIN = join_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_update_obj(self, update_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Update对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param update_obj<Update> Update对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(update_obj, Update):
|
|||
|
|
raise PanelError('update_obj must a instance of core.include.sqlite_server.Update')
|
|||
|
|
self.__OPT_UPDATE = update_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_duplicate_obj(self, duplicate_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置Duplicated对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param duplicate_obj<Duplicate> Duplicate对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(duplicate_obj, Duplicate):
|
|||
|
|
raise PanelError('duplicate_obj must a instance of core.include.sqlite_server.Duplicate')
|
|||
|
|
self.__OPT_DUPLICATE = duplicate_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def set_alter_table_obj(self, alter_table_obj):
|
|||
|
|
'''
|
|||
|
|
@name 设置AlterTable对象
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@param alter_table_obj<AlterTable> AlterTable对象
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(alter_table_obj, AlterTable):
|
|||
|
|
raise PanelError('alter_table_obj must a instance of core.include.sqlite_server.AlterTable')
|
|||
|
|
self.__OPT_ALTER_TABLE = alter_table_obj
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
'''
|
|||
|
|
@name 关闭游标并将数据库连接返回连接池
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
try:
|
|||
|
|
self.__DB.query_done()
|
|||
|
|
self.__DB = None
|
|||
|
|
|
|||
|
|
# 清理查询条件
|
|||
|
|
self.__clear()
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def autocommit(self, autocommit=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置自动提交事务状态
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param autocommit<bool> 是否自动提交事务
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if self.__DB is not None:
|
|||
|
|
self.__DB.autocommit(autocommit)
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def commit(self):
|
|||
|
|
'''
|
|||
|
|
@name 提交事务
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 提交事务
|
|||
|
|
return self.__DB.commit()
|
|||
|
|
|
|||
|
|
def rollback(self):
|
|||
|
|
'''
|
|||
|
|
@name 回滚事务
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 回滚事务
|
|||
|
|
return self.__DB.rollback()
|
|||
|
|
|
|||
|
|
def vacuum(self):
|
|||
|
|
"""
|
|||
|
|
@name 释放空间
|
|||
|
|
@author Zhj<2023-11-13>
|
|||
|
|
@return bool
|
|||
|
|
"""
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return self.__DB.vacuum()
|
|||
|
|
|
|||
|
|
def fetch_sql(self, fetch_sql=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置是否输入sql原生语句
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@param fetch_sql<bool> 是否输入sql原生语句
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__FETCH_SQL = fetch_sql
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def explain(self, explain=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置EXPLAIN
|
|||
|
|
@author Zhj<2022-07-20>
|
|||
|
|
@param explain<bool> 是否开启EXPLAIN
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__EXPLAIN = explain
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def prefix(self, prefix):
|
|||
|
|
'''
|
|||
|
|
@name 设置表前缀
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param prefix<string> 表前缀
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_PREFIX = prefix
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def name(self, table_name):
|
|||
|
|
'''
|
|||
|
|
@name 设置表名(不包含前缀)
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param table_name<string> 表名(不包含前缀)
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
return self.table(table_name, False)
|
|||
|
|
|
|||
|
|
def table(self, table_name, is_fullname=True):
|
|||
|
|
'''
|
|||
|
|
@name 设置表名(包含前缀)
|
|||
|
|
@param table_name<string> 表名
|
|||
|
|
@param is_fullname<bool> 是否完整表名(不包含前缀)
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
m = match_table_name_reg.match(table_name)
|
|||
|
|
|
|||
|
|
if m is None or m.group(1) is None:
|
|||
|
|
raise PanelError('Invalid table name:{}'.format(table_name))
|
|||
|
|
|
|||
|
|
self.__DB_TABLE = m.group(1).strip('`')
|
|||
|
|
|
|||
|
|
# 添加前缀
|
|||
|
|
if not is_fullname and self.__OPT_PREFIX is not None:
|
|||
|
|
self.__DB_TABLE = self.__OPT_PREFIX + self.__DB_TABLE
|
|||
|
|
|
|||
|
|
if m.group(2) is not None:
|
|||
|
|
self.__OPT_ALIAS = m.group(2).strip('`')
|
|||
|
|
|
|||
|
|
# 重置主键字段名
|
|||
|
|
self.__PK = None
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
# 使用子查询
|
|||
|
|
def from_sub_query(self, sub_query):
|
|||
|
|
# 当传入查询构造器对象时,将其转为SQL语句
|
|||
|
|
if isinstance(sub_query, SqliteEasy):
|
|||
|
|
sub_query = sub_query.build_sql(True)
|
|||
|
|
|
|||
|
|
m = search_subquery_reg.search(str(sub_query))
|
|||
|
|
|
|||
|
|
if m is None or m.group(1) is None:
|
|||
|
|
raise PanelError('Invalid sub-query:{}'.format(sub_query))
|
|||
|
|
|
|||
|
|
self.__DB_TABLE = m.group(1)
|
|||
|
|
|
|||
|
|
if m.group(2) is not None:
|
|||
|
|
self.__OPT_ALIAS = m.group(2).strip('`')
|
|||
|
|
|
|||
|
|
# 重置主键字段名
|
|||
|
|
self.__PK = None
|
|||
|
|
|
|||
|
|
self.__FROM_SUB_QUERY = True
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def alias(self, alias):
|
|||
|
|
'''
|
|||
|
|
@name 设置表别名
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param alias<string> 表别名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_ALIAS = alias
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def field(self, *fields):
|
|||
|
|
'''
|
|||
|
|
@name 添加查询字段
|
|||
|
|
@author Zhj<2022-07-15>
|
|||
|
|
@param fields<tuple> 查询字段列表
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_FIELD.add_fields(*fields)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def join(self, exp, condition, join_type='INNER', add_table_prefix=True):
|
|||
|
|
'''
|
|||
|
|
@name 添加关联条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param exp<string> 表达式
|
|||
|
|
@param condition<string> 关联条件
|
|||
|
|
@param join_type<string> 关联方式 INNER|LEFT|RIGHT
|
|||
|
|
@param add_table_prefix<bool> 是否添加表前缀[可选 默认自动添加]
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
table_prefix = ''
|
|||
|
|
|
|||
|
|
if add_table_prefix and self.__OPT_PREFIX is not None:
|
|||
|
|
table_prefix = self.__OPT_PREFIX
|
|||
|
|
|
|||
|
|
self.__OPT_JOIN.add_join(exp, condition, join_type, table_prefix)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def inner_join(self, exp, condition, add_table_prefix=True):
|
|||
|
|
'''
|
|||
|
|
@name 添加内连接关联条件
|
|||
|
|
@param exp<string> 表达式
|
|||
|
|
@param condition<string> 关联条件
|
|||
|
|
@param add_table_prefix<bool> 是否添加表前缀[可选 默认自动添加]
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
return self.join(exp, condition, 'INNER', add_table_prefix)
|
|||
|
|
|
|||
|
|
def left_join(self, exp, condition, add_table_prefix=True):
|
|||
|
|
'''
|
|||
|
|
@name 添加左连接关联条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param exp<string> 表达式
|
|||
|
|
@param condition<string> 关联条件
|
|||
|
|
@param add_table_prefix<bool> 是否添加表前缀[可选 默认自动添加]
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
return self.join(exp, condition, 'LEFT', add_table_prefix)
|
|||
|
|
|
|||
|
|
def right_join(self, exp, condition, add_table_prefix=True):
|
|||
|
|
'''
|
|||
|
|
@name 添加右连接关联条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param exp<string> 表达式
|
|||
|
|
@param condition<string> 关联条件
|
|||
|
|
@param add_table_prefix<bool> 是否添加表前缀[可选 默认自动添加]
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
return self.join(exp, condition, 'RIGHT', add_table_prefix)
|
|||
|
|
|
|||
|
|
def where(self, condition, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加where条件 AND
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param condition<string> where条件
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_WHERE.add(condition, params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def where_or(self, condition, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加where条件 OR
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param condition<string> where条件
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_WHERE.add(condition, params, 'OR')
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def where_in(self, field, vals, logic='AND'):
|
|||
|
|
'''
|
|||
|
|
@name 添加where条件 IN
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param vals<list|tuple> 查询参数列表
|
|||
|
|
@param logic<string> 逻辑运算符 AND|OR
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_WHERE.add_where_in(field, vals, logic)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def where_not_in(self, field, vals, logic='AND'):
|
|||
|
|
'''
|
|||
|
|
@name 添加where条件 IN
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param vals<list|tuple> 查询参数列表
|
|||
|
|
@param logic<string> 逻辑运算符 AND|OR
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_WHERE.add_where_in(field, vals, logic, True)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
# 嵌套where
|
|||
|
|
@contextmanager
|
|||
|
|
def where_nest(self, logic: str = 'and'):
|
|||
|
|
query = SqliteEasy(self.__DB)
|
|||
|
|
yield query
|
|||
|
|
self.__OPT_WHERE.add_nest(query.get_where_obj(), logic)
|
|||
|
|
|
|||
|
|
def group(self, condition, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加分组条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param condition<string> 分组条件
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_GROUP.add_group(condition, params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def having(self, condition, params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加分组筛选条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param condition<string> 分组筛选条件
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_HAVING.add_having(condition, params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def order(self, field, ordering='ASC', params=()):
|
|||
|
|
'''
|
|||
|
|
@name 添加排序条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param ordering<string> 排序方式 ASC|DESC
|
|||
|
|
@param params<list|tuple> 绑定参数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_ORDER.add_order(field, ordering, params)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def limit(self, limit, skip=None):
|
|||
|
|
'''
|
|||
|
|
@name 设置返回行数
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param limit<integer> 返回的行数
|
|||
|
|
@param skip<?integer> 跳过的行数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_LIMIT.set_limit(limit)
|
|||
|
|
|
|||
|
|
if skip is not None:
|
|||
|
|
self.__OPT_LIMIT.set_skip(skip)
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def skip(self, skip):
|
|||
|
|
'''
|
|||
|
|
@name 设置跳过的行数
|
|||
|
|
@author Zhj<2022-07-16>
|
|||
|
|
@param skip<integer> 跳过的行数
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_LIMIT.set_skip(skip)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def query(self, raw_sql, params=(), take_first=False, clear_conditions=True):
|
|||
|
|
'''
|
|||
|
|
@name 查询
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param raw_sql<string> sql语句
|
|||
|
|
@param params<list|tuple> 绑定参数[可选]
|
|||
|
|
@param take_first<bool> 是否只获取一行数据[可选 默认获取所有行]
|
|||
|
|
@param clear_conditions<?bool> 是否清空查询条件[可选 默认清空]
|
|||
|
|
@return list|dict|None
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 记录语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
ret = self.__DB.client().query(raw_sql, _to_tuple(params), take_first)
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.__DB.is_debug():
|
|||
|
|
self.__DB.debug_log(self.__to_raw_sql(raw_sql, _to_tuple(params)), self.__DB.db_name(),
|
|||
|
|
time.time() - s_time)
|
|||
|
|
|
|||
|
|
# 自动提交事务
|
|||
|
|
# if self.__is_autocommit():
|
|||
|
|
# self.commit()
|
|||
|
|
|
|||
|
|
# 清空查询条件
|
|||
|
|
if clear_conditions:
|
|||
|
|
self.__clear()
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def execute(self, raw_sql, params=(), get_rowid=False, clear_conditions=True):
|
|||
|
|
'''
|
|||
|
|
@name 执行一条sql语句并返回影响的行数
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@param raw_sql<string> sql语句
|
|||
|
|
@param params<list|tuple> 绑定参数[可选]
|
|||
|
|
@param get_rowid<bool> 获取插入ID[可选 默认返回影响的行数]
|
|||
|
|
@param clear_conditions<?bool> 是否清空查询条件[可选 默认清空]
|
|||
|
|
@return integer
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
# 记录语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
ret = self.__DB.client().execute(raw_sql, _to_tuple(params), get_rowid)
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.__DB.is_debug():
|
|||
|
|
self.__DB.debug_log(self.__to_raw_sql(raw_sql, _to_tuple(params)), self.__DB.db_name(),
|
|||
|
|
time.time() - s_time)
|
|||
|
|
|
|||
|
|
# 自动提交事务
|
|||
|
|
if self.__is_autocommit():
|
|||
|
|
self.commit()
|
|||
|
|
|
|||
|
|
# 清空查询条件
|
|||
|
|
if clear_conditions:
|
|||
|
|
self.__clear()
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def execute_script(self, sql_script, clear_conditions=True):
|
|||
|
|
'''
|
|||
|
|
@name 执行多条sql语句(注意:执行这个方法会自动提交之前的事务,本次执行不会加入事务,事务请编写在sql脚本中)
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@param sql_script<string> sql语句
|
|||
|
|
@param clear_conditions<?bool> 是否清空查询条件[可选 默认清空]
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 记录语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
self.__DB.client().execute_script(sql_script)
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.__DB.is_debug():
|
|||
|
|
self.__DB.debug_log(sql_script, self.__DB.db_name(), time.time() - s_time)
|
|||
|
|
|
|||
|
|
# 清空查询条件
|
|||
|
|
if clear_conditions:
|
|||
|
|
self.__clear()
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def insert(self, data, option=None, get_rowid=True):
|
|||
|
|
'''
|
|||
|
|
@name 插入一条数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param data<dict|list> 插入数据
|
|||
|
|
@param option<?string> 额外选项[可选]
|
|||
|
|
@param get_rowid<bool> 获取插入ID[可选 默认返回影响的行数]
|
|||
|
|
@return integer|string
|
|||
|
|
'''
|
|||
|
|
if self.__DB_TABLE is None:
|
|||
|
|
raise PanelError('Insert failed: table name not provide.')
|
|||
|
|
|
|||
|
|
# 传入dict类型之外的情况
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
# list类型:尝试insert_all
|
|||
|
|
if isinstance(data, list):
|
|||
|
|
return self.insert_all(data, option=option)
|
|||
|
|
|
|||
|
|
# 其它类型:抛出异常提示
|
|||
|
|
raise PanelError('Insert failed: parameter "data" type must be dict.')
|
|||
|
|
|
|||
|
|
# 检查冲突选项是否正确
|
|||
|
|
if option is not None and str(option).upper() not in self.__CONFLICT_OPTIONS:
|
|||
|
|
raise PanelError('option must be one of: {}'.format(', '.join(self.__CONFLICT_OPTIONS)))
|
|||
|
|
|
|||
|
|
placeholders = []
|
|||
|
|
ks = data.keys()
|
|||
|
|
params = ()
|
|||
|
|
|
|||
|
|
for k in ks:
|
|||
|
|
placeholders.append('?')
|
|||
|
|
params += (data[k],)
|
|||
|
|
|
|||
|
|
# build deplicate sql and bind params
|
|||
|
|
duplicate_sql, duplicate_binds = self.__OPT_DUPLICATE.build()
|
|||
|
|
|
|||
|
|
params += duplicate_binds
|
|||
|
|
|
|||
|
|
raw_sql = 'INSERT{} INTO {} ({}) VALUES ({}){}'.format(
|
|||
|
|
' OR {}'.format(str(option).upper()) if option is not None else '',
|
|||
|
|
_add_backtick_for_field(self.__DB_TABLE),
|
|||
|
|
', '.join(list(map(lambda x: _add_backtick_for_field(x), ks))),
|
|||
|
|
','.join(placeholders),
|
|||
|
|
duplicate_sql
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 输出sql原生语句
|
|||
|
|
if self.__FETCH_SQL:
|
|||
|
|
return self.__to_raw_sql(raw_sql, params)
|
|||
|
|
|
|||
|
|
# 输出SQL语句分析信息
|
|||
|
|
if self.__EXPLAIN:
|
|||
|
|
return self.explain_raw_sql(raw_sql, params)
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
return self.execute(raw_sql, params, get_rowid)
|
|||
|
|
|
|||
|
|
def insert_all(self, data_list, clear_conditions=True, option=None):
|
|||
|
|
'''
|
|||
|
|
@name 批量插入数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param data_list<list> 批量插入数据
|
|||
|
|
@param clear_conditions<?bool> 是否清空查询条件[可选 默认清空]
|
|||
|
|
@param option<?string> 额外选项[可选]
|
|||
|
|
@return integer
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None or len(data_list) == 0:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
# 检查冲突选项是否正确
|
|||
|
|
if option is not None and str(option).upper() not in self.__CONFLICT_OPTIONS:
|
|||
|
|
raise PanelError('option must be one of: {}'.format(', '.join(self.__CONFLICT_OPTIONS)))
|
|||
|
|
|
|||
|
|
ks = data_list[0].keys()
|
|||
|
|
|
|||
|
|
# build deplicate sql and bind params
|
|||
|
|
duplicate_sql, duplicate_binds = self.__OPT_DUPLICATE.build()
|
|||
|
|
|
|||
|
|
# 生成sql语句
|
|||
|
|
raw_sql = 'INSERT{} INTO {} ({}) VALUES ({}){}'.format(
|
|||
|
|
' OR {}'.format(str(option).upper()) if option is not None else '',
|
|||
|
|
_add_backtick_for_field(self.__DB_TABLE),
|
|||
|
|
', '.join(list(map(lambda x: _add_backtick_for_field(x), ks))),
|
|||
|
|
','.join(list(map(lambda x: '?', ks))),
|
|||
|
|
duplicate_sql
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 绑定参数
|
|||
|
|
params = list(map(lambda x: _to_tuple(list(map(lambda y: x[y], ks))), data_list))
|
|||
|
|
|
|||
|
|
params += duplicate_binds
|
|||
|
|
|
|||
|
|
# 记录语句执行开始时间
|
|||
|
|
s_time = time.time()
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
ret = self.__DB.client().execute_many(raw_sql, params)
|
|||
|
|
|
|||
|
|
# 写日志
|
|||
|
|
if self.__DB.is_debug():
|
|||
|
|
# 构造一个真实的批量写入sql
|
|||
|
|
debug_raw_sql = raw_sql
|
|||
|
|
i = len(params) - 1
|
|||
|
|
pad_str = ', ({})'.format(','.join(list(map(lambda x: '?', ks))))
|
|||
|
|
|
|||
|
|
while i:
|
|||
|
|
debug_raw_sql += pad_str
|
|||
|
|
i -= 1
|
|||
|
|
|
|||
|
|
# 将绑定参数扁平化
|
|||
|
|
debug_params = ()
|
|||
|
|
for p in params:
|
|||
|
|
debug_params += p
|
|||
|
|
|
|||
|
|
self.__DB.debug_log(self.__to_raw_sql(debug_raw_sql, _to_tuple(debug_params)), self.__DB.db_name(),
|
|||
|
|
time.time() - s_time)
|
|||
|
|
|
|||
|
|
# 自动提交事务
|
|||
|
|
if self.__is_autocommit():
|
|||
|
|
self.commit()
|
|||
|
|
|
|||
|
|
# 清空查询条件
|
|||
|
|
if clear_conditions:
|
|||
|
|
self.__clear()
|
|||
|
|
|
|||
|
|
# 返回新增的行数
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def increment(self, field, step=1):
|
|||
|
|
'''
|
|||
|
|
@name 自增数值
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param step<integer> 值
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_UPDATE.increment(field, step)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def decrement(self, field, step=1):
|
|||
|
|
'''
|
|||
|
|
@name 自减数值
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param step<integer> 值
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_UPDATE.decrement(field, step)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def exp(self, field, exp):
|
|||
|
|
'''
|
|||
|
|
@name 使用原生表达式更新
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param exp<string> 原生表达式
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_UPDATE.exp(field, exp)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def update(self, data=None):
|
|||
|
|
'''
|
|||
|
|
@name 更新表数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param data<?dict> 更新数据
|
|||
|
|
@return integer|string
|
|||
|
|
'''
|
|||
|
|
if data is not None:
|
|||
|
|
# 更新数据不是字典类型 返回0
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
for k, v in data.items():
|
|||
|
|
self.__OPT_UPDATE.add(k, v)
|
|||
|
|
|
|||
|
|
# 更新条件为空 返回0
|
|||
|
|
if self.__OPT_UPDATE.is_empty():
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
update_str, update_params = self.__OPT_UPDATE.build()
|
|||
|
|
join_str = self.__OPT_JOIN.build()
|
|||
|
|
where_str, where_params = self.__OPT_WHERE.build()
|
|||
|
|
limit_str = self.__OPT_LIMIT.build()
|
|||
|
|
|
|||
|
|
raw_sql = 'UPDATE {table_name}{join_condition} SET {exprission}' \
|
|||
|
|
'{where_condition}{limit_condition}'.format_map({
|
|||
|
|
'table_name': self.__build_table_name(True),
|
|||
|
|
'join_condition': join_str,
|
|||
|
|
'exprission': update_str,
|
|||
|
|
'where_condition': where_str,
|
|||
|
|
'limit_condition': limit_str,
|
|||
|
|
})
|
|||
|
|
bind_params = update_params + where_params
|
|||
|
|
|
|||
|
|
# 输出原生sql语句
|
|||
|
|
if self.__FETCH_SQL:
|
|||
|
|
return self.__to_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
# 输出SQL语句分析信息
|
|||
|
|
if self.__EXPLAIN:
|
|||
|
|
return self.explain_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
return self.execute(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
def delete(self):
|
|||
|
|
'''
|
|||
|
|
@name 删除表数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return integer|string
|
|||
|
|
'''
|
|||
|
|
join_str = self.__OPT_JOIN.build()
|
|||
|
|
where_str, where_params = self.__OPT_WHERE.build()
|
|||
|
|
limit_str = self.__OPT_LIMIT.build()
|
|||
|
|
|
|||
|
|
raw_sql = 'DELETE FROM {table_name}{join_condition}{where_condition}{limit_condition}'.format(
|
|||
|
|
table_name=self.__build_table_name(True),
|
|||
|
|
join_condition=join_str,
|
|||
|
|
where_condition=where_str,
|
|||
|
|
limit_condition=limit_str
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 输出原生sql语句
|
|||
|
|
if self.__FETCH_SQL:
|
|||
|
|
return self.__to_raw_sql(raw_sql, where_params)
|
|||
|
|
|
|||
|
|
# 输出SQL语句分析信息
|
|||
|
|
if self.__EXPLAIN:
|
|||
|
|
return self.explain_raw_sql(raw_sql, where_params)
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
ret = self.execute(raw_sql, where_params)
|
|||
|
|
|
|||
|
|
# 自动提交事务时
|
|||
|
|
# 且开启了自动释放空间时
|
|||
|
|
# 释放空间
|
|||
|
|
if self.__is_autocommit() and self.__is_autovacuum():
|
|||
|
|
self.__DB.vacuum()
|
|||
|
|
# 否则
|
|||
|
|
# 记录本次事务提交后需要释放空间
|
|||
|
|
else:
|
|||
|
|
self.__DB.need_vacuum()
|
|||
|
|
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
def find(self):
|
|||
|
|
'''
|
|||
|
|
@name 查询一行数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return dict|None|string
|
|||
|
|
'''
|
|||
|
|
self.__OPT_LIMIT.set_limit(1)
|
|||
|
|
|
|||
|
|
# 构建sql语句和绑定参数
|
|||
|
|
raw_sql, bind_params = self.__build_sql()
|
|||
|
|
|
|||
|
|
# 输出原生sql语句
|
|||
|
|
if self.__FETCH_SQL:
|
|||
|
|
return self.__to_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
# 输出SQL语句分析信息
|
|||
|
|
if self.__EXPLAIN:
|
|||
|
|
return self.explain_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
return self.query(raw_sql, bind_params, True)
|
|||
|
|
|
|||
|
|
def select(self):
|
|||
|
|
'''
|
|||
|
|
@name 查询多行数据
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return list|None|string
|
|||
|
|
'''
|
|||
|
|
# 构建sql语句和绑定参数
|
|||
|
|
raw_sql, bind_params = self.__build_sql()
|
|||
|
|
|
|||
|
|
# 输出原生sql语句
|
|||
|
|
if self.__FETCH_SQL:
|
|||
|
|
return self.__to_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
# 输出SQL语句分析信息
|
|||
|
|
if self.__EXPLAIN:
|
|||
|
|
return self.explain_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
return self.query(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
def value(self, field):
|
|||
|
|
'''
|
|||
|
|
@name 获取某个字段的值
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@return string|integer|None
|
|||
|
|
'''
|
|||
|
|
self.__OPT_FIELD.set_fields(field)
|
|||
|
|
ret = self.find()
|
|||
|
|
|
|||
|
|
# 输出原生sql语句或SQL语句分析信息
|
|||
|
|
if self.__FETCH_SQL or self.__EXPLAIN:
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
return ret.get(self.__get_row_key(field), None)
|
|||
|
|
|
|||
|
|
def column(self, field, dict_key=None):
|
|||
|
|
'''
|
|||
|
|
@name 获取指定字段的值列表或字典
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string|None> 字段名
|
|||
|
|
@param dict_key<?string> 字典键(字段名)
|
|||
|
|
@return list|dict|string
|
|||
|
|
'''
|
|||
|
|
if self.__OPT_FIELD.is_empty() and field is not None:
|
|||
|
|
self.__OPT_FIELD.set_fields(*filter(lambda x: x is not None, [field, dict_key]))
|
|||
|
|
|
|||
|
|
ret = self.select()
|
|||
|
|
|
|||
|
|
# 输出原生sql语句或SQL语句分析信息
|
|||
|
|
if self.__FETCH_SQL or self.__EXPLAIN:
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
# 指定了键时 返回字典
|
|||
|
|
if dict_key is not None:
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
# 返回列表
|
|||
|
|
if dict_key is None:
|
|||
|
|
return list(map(lambda x: x.get(self.__get_row_key(field), None), ret))
|
|||
|
|
|
|||
|
|
# 返回字典
|
|||
|
|
d = {}
|
|||
|
|
f_k = None if field is None else self.__get_row_key(field)
|
|||
|
|
d_k = self.__get_row_key(dict_key)
|
|||
|
|
|
|||
|
|
for item in ret:
|
|||
|
|
k = item.get(d_k, None)
|
|||
|
|
|
|||
|
|
if k is None:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
d[k] = item if f_k is None else item.get(f_k, None)
|
|||
|
|
|
|||
|
|
return d
|
|||
|
|
|
|||
|
|
def count(self):
|
|||
|
|
'''
|
|||
|
|
@name 统计行数
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return integer
|
|||
|
|
'''
|
|||
|
|
ret = self.value('COUNT(*)')
|
|||
|
|
|
|||
|
|
# 输出原生sql语句或SQL语句分析信息
|
|||
|
|
if self.__FETCH_SQL or self.__EXPLAIN:
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
ret = 0
|
|||
|
|
|
|||
|
|
return int(ret)
|
|||
|
|
|
|||
|
|
def avg(self, field, precsicion=None):
|
|||
|
|
'''
|
|||
|
|
@name 统计平均值
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param precision<?integer> 小数点精度
|
|||
|
|
@return float
|
|||
|
|
'''
|
|||
|
|
field = 'AVG({})'.format(_add_backtick_for_field(field))
|
|||
|
|
|
|||
|
|
if precsicion is not None and is_number(precsicion):
|
|||
|
|
field = 'ROUND({},{})'.format(field, precsicion)
|
|||
|
|
|
|||
|
|
ret = self.value(field)
|
|||
|
|
|
|||
|
|
# 输出原生sql语句或SQL语句分析信息
|
|||
|
|
if self.__FETCH_SQL or self.__EXPLAIN:
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
ret = 0
|
|||
|
|
|
|||
|
|
return float(ret)
|
|||
|
|
|
|||
|
|
def sum(self, field, precsicion=None):
|
|||
|
|
'''
|
|||
|
|
@name 统计总和
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@param precision<?integer> 小数点精度
|
|||
|
|
@return integer
|
|||
|
|
'''
|
|||
|
|
field = 'SUM({})'.format(_add_backtick_for_field(field))
|
|||
|
|
|
|||
|
|
if precsicion is not None and is_number(precsicion):
|
|||
|
|
field = 'ROUND({},{})'.format(field, precsicion)
|
|||
|
|
|
|||
|
|
ret = self.value(field)
|
|||
|
|
|
|||
|
|
# 输出原生sql语句或SQL语句分析信息
|
|||
|
|
if self.__FETCH_SQL or self.__EXPLAIN:
|
|||
|
|
return ret
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
ret = 0
|
|||
|
|
|
|||
|
|
return int(ret)
|
|||
|
|
|
|||
|
|
def exists(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查数据是否存在
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
self.__OPT_LIMIT.set_limit(1)
|
|||
|
|
|
|||
|
|
# 构建sql语句和绑定参数
|
|||
|
|
raw_sql, bind_params = self.__build_sql()
|
|||
|
|
|
|||
|
|
k = 'bt__exists'
|
|||
|
|
|
|||
|
|
raw_sql = 'SELECT EXISTS({}) AS `{}`'.format(raw_sql, k)
|
|||
|
|
|
|||
|
|
# 输出原生sql语句
|
|||
|
|
if self.__FETCH_SQL:
|
|||
|
|
return self.__to_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
# 输出SQL语句分析信息
|
|||
|
|
if self.__EXPLAIN:
|
|||
|
|
return self.explain_raw_sql(raw_sql, bind_params)
|
|||
|
|
|
|||
|
|
# 执行sql语句
|
|||
|
|
ret = self.query(raw_sql, bind_params, True)
|
|||
|
|
|
|||
|
|
if ret is None or not isinstance(ret, dict):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return True if int(ret.get(k, 0)) == 1 else False
|
|||
|
|
|
|||
|
|
# 设置insert时唯一索引重复时的更新操作
|
|||
|
|
def duplicate(self, update: typing.Dict[str, str]):
|
|||
|
|
self.__OPT_DUPLICATE.clear()
|
|||
|
|
for k, v in update.items():
|
|||
|
|
self.__OPT_DUPLICATE.exp(k, v)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
# TODO 强制索引
|
|||
|
|
def force_index(self, index_name: str):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def build_sql(self, sub_query=False, alias=None):
|
|||
|
|
'''
|
|||
|
|
@name 构建sql查询语句(合并绑定参数)
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param sub_query<bool> 是否为子查询
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
raw_sql = self.__to_raw_sql(*self.__build_sql())
|
|||
|
|
|
|||
|
|
# 子查询
|
|||
|
|
if sub_query:
|
|||
|
|
raw_sql = '(%s)' % raw_sql
|
|||
|
|
|
|||
|
|
# 别名
|
|||
|
|
if alias is not None:
|
|||
|
|
raw_sql = '%s AS %s' % (raw_sql, _add_backtick_for_field(alias))
|
|||
|
|
|
|||
|
|
return raw_sql
|
|||
|
|
|
|||
|
|
def get_pk(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取主键字段名
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@return string|None
|
|||
|
|
'''
|
|||
|
|
if self.__PK is not None:
|
|||
|
|
return self.__PK
|
|||
|
|
|
|||
|
|
ret = self.query('PRAGMA TABLE_INFO({})'.format(self.__build_table_name(False)), take_first=True,
|
|||
|
|
clear_conditions=False)
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
self.__PK = ret.get('name', None)
|
|||
|
|
|
|||
|
|
return self.__PK
|
|||
|
|
|
|||
|
|
def get_columns(self):
|
|||
|
|
'''
|
|||
|
|
@name 获取所有列名
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@return list
|
|||
|
|
'''
|
|||
|
|
ret = self.query('PRAGMA TABLE_INFO({})'.format(self.__build_table_name(False)), clear_conditions=False)
|
|||
|
|
|
|||
|
|
return [column['name'] for column in ret]
|
|||
|
|
|
|||
|
|
def add_column(self, col_name, prop, force=False):
|
|||
|
|
'''
|
|||
|
|
@name 新建字段
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 字段名
|
|||
|
|
@param prop<string> 字段属性
|
|||
|
|
@param force<?bool> 是否强制新增(删除旧的字段)[可选]
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_ALTER_TABLE.add_column(col_name, prop, force)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def drop_column(self, col_name):
|
|||
|
|
'''
|
|||
|
|
@name 删除字段
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 字段名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_ALTER_TABLE.drop_column(col_name)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def rename_column(self, col_name, new_col_name):
|
|||
|
|
'''
|
|||
|
|
@name 更新字段名
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@param col_name<string> 当前字段名
|
|||
|
|
@param new_col_name<string> 新字段名
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.__OPT_ALTER_TABLE.rename_column(col_name, new_col_name)
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def alter_table(self):
|
|||
|
|
'''
|
|||
|
|
@name 更新表结构
|
|||
|
|
@author Zhj<2022-09-21>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__OPT_ALTER_TABLE.is_empty():
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
raw_sql = self.__OPT_ALTER_TABLE.build(self.__build_table_name(False))
|
|||
|
|
|
|||
|
|
self.execute_script(raw_sql)
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def add_index(self, idx_name, idx_col):
|
|||
|
|
'''
|
|||
|
|
@name 创建索引
|
|||
|
|
@author Zhj<2022-10-11>
|
|||
|
|
@param idx_name<string> 索引名称
|
|||
|
|
@param idx_col<string|list> 字段名称
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(idx_col, list):
|
|||
|
|
idx_col = [idx_col]
|
|||
|
|
|
|||
|
|
self.execute('CREATE INDEX IF NOT EXISTS {} ON {} ({})'.format(
|
|||
|
|
_add_backtick_for_field(idx_name),
|
|||
|
|
self.__build_table_name(False),
|
|||
|
|
','.join(list(map(lambda x: _add_backtick_for_field(x), idx_col)))
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def drop_index(self, idx_name):
|
|||
|
|
'''
|
|||
|
|
@name 删除索引
|
|||
|
|
@author Zhj<2022-10-11>
|
|||
|
|
@param idx_name<string> 索引名称
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
self.execute('DROP INDEX IF EXISTS {} ON {}'.format(
|
|||
|
|
_add_backtick_for_field(idx_name),
|
|||
|
|
self.__build_table_name(False)
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def fork(self):
|
|||
|
|
'''
|
|||
|
|
@name 克隆一个查询构造器
|
|||
|
|
@author Zhj<2022-07-19>
|
|||
|
|
@return SqliteEasy|None
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
query = self.__DB.query()
|
|||
|
|
query.set_pk(self.__PK)
|
|||
|
|
if self.__FROM_SUB_QUERY:
|
|||
|
|
query.from_sub_query(self.__DB_TABLE)
|
|||
|
|
else:
|
|||
|
|
query.table(self.__DB_TABLE)
|
|||
|
|
query.prefix(self.__OPT_PREFIX)
|
|||
|
|
query.alias(self.__OPT_ALIAS)
|
|||
|
|
query.set_where_obj(copy.deepcopy(self.__OPT_WHERE))
|
|||
|
|
query.set_limit_obj(copy.deepcopy(self.__OPT_LIMIT))
|
|||
|
|
query.set_order_obj(copy.deepcopy(self.__OPT_ORDER))
|
|||
|
|
query.set_field_obj(copy.deepcopy(self.__OPT_FIELD))
|
|||
|
|
query.set_group_obj(copy.deepcopy(self.__OPT_GROUP))
|
|||
|
|
query.set_having_obj(copy.deepcopy(self.__OPT_HAVING))
|
|||
|
|
query.set_join_obj(copy.deepcopy(self.__OPT_JOIN))
|
|||
|
|
query.set_update_obj(copy.deepcopy(self.__OPT_UPDATE))
|
|||
|
|
query.set_duplicate_obj(copy.deepcopy(self.__OPT_DUPLICATE))
|
|||
|
|
|
|||
|
|
return query
|
|||
|
|
|
|||
|
|
def snapshot(self):
|
|||
|
|
'''
|
|||
|
|
@name 创建查询构造器快照
|
|||
|
|
@author Zhj<2025-01-07>
|
|||
|
|
@return SqliteEasy|None
|
|||
|
|
'''
|
|||
|
|
snapshot = Snapshot()
|
|||
|
|
snapshot.pk = self.__PK
|
|||
|
|
snapshot.from_sub_query = self.__FROM_SUB_QUERY
|
|||
|
|
snapshot.table = self.__DB_TABLE
|
|||
|
|
snapshot.prefix = self.__OPT_PREFIX
|
|||
|
|
snapshot.alias = self.__OPT_ALIAS
|
|||
|
|
snapshot.where = copy.deepcopy(self.__OPT_WHERE)
|
|||
|
|
snapshot.limit = copy.deepcopy(self.__OPT_LIMIT)
|
|||
|
|
snapshot.order = copy.deepcopy(self.__OPT_ORDER)
|
|||
|
|
snapshot.field = copy.deepcopy(self.__OPT_FIELD)
|
|||
|
|
snapshot.group = copy.deepcopy(self.__OPT_GROUP)
|
|||
|
|
snapshot.having = copy.deepcopy(self.__OPT_HAVING)
|
|||
|
|
snapshot.join = copy.deepcopy(self.__OPT_JOIN)
|
|||
|
|
snapshot.update = copy.deepcopy(self.__OPT_UPDATE)
|
|||
|
|
snapshot.duplicate = copy.deepcopy(self.__OPT_DUPLICATE)
|
|||
|
|
return snapshot
|
|||
|
|
|
|||
|
|
def restore_from_snapshot(self, snapshot):
|
|||
|
|
'''
|
|||
|
|
@name 通过快照还原查询构造器
|
|||
|
|
@author Zhj<2025-01-07>
|
|||
|
|
@return self
|
|||
|
|
'''
|
|||
|
|
if not isinstance(snapshot, Snapshot):
|
|||
|
|
raise PanelError('snapshot must a instance of core.include.sqlite_server.Snapshot')
|
|||
|
|
self.set_pk(snapshot.pk)
|
|||
|
|
if snapshot.from_sub_query:
|
|||
|
|
self.from_sub_query(snapshot.table)
|
|||
|
|
else:
|
|||
|
|
self.table(snapshot.table)
|
|||
|
|
self.prefix(snapshot.prefix)
|
|||
|
|
self.alias(snapshot.alias)
|
|||
|
|
self.set_where_obj(copy.deepcopy(snapshot.where))
|
|||
|
|
self.set_limit_obj(copy.deepcopy(snapshot.limit))
|
|||
|
|
self.set_order_obj(copy.deepcopy(snapshot.order))
|
|||
|
|
self.set_field_obj(copy.deepcopy(snapshot.field))
|
|||
|
|
self.set_group_obj(copy.deepcopy(snapshot.group))
|
|||
|
|
self.set_having_obj(copy.deepcopy(snapshot.having))
|
|||
|
|
self.set_join_obj(copy.deepcopy(snapshot.join))
|
|||
|
|
self.set_update_obj(copy.deepcopy(snapshot.update))
|
|||
|
|
self.set_duplicate_obj(copy.deepcopy(snapshot.duplicate))
|
|||
|
|
# self.set_alter_table_obj(copy.deepcopy(snapshot.alter_table))
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
def explain_raw_sql(self, raw_sql, bind_params=()):
|
|||
|
|
'''
|
|||
|
|
@name 分析SQL语句
|
|||
|
|
@author Zhj<2022-07-21>
|
|||
|
|
@param raw_sql<string> SQL语句
|
|||
|
|
@param bind_params<list|tuple> 绑定参数
|
|||
|
|
@return string|None
|
|||
|
|
'''
|
|||
|
|
ret = self.query('EXPLAIN QUERY PLAN {}'.format(raw_sql), _to_tuple(bind_params))
|
|||
|
|
|
|||
|
|
if ret is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
return "\n".join(list(map(lambda x: x.get('detail', ''), ret)))
|
|||
|
|
|
|||
|
|
def __to_raw_sql(self, raw_sql, bind_params=()):
|
|||
|
|
'''
|
|||
|
|
@name 将sql语句和绑定参数合并
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@param raw_sql<string> sql语句(含有参数绑定占位符)
|
|||
|
|
@param bind_params<tuple> 绑定参数
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
return reduce(lambda x, y: search_question_reg.sub(str(y) if is_number(y) else "'%s'" % y, x, 1), bind_params, repr(raw_sql)[1:-1])
|
|||
|
|
|
|||
|
|
def __build_sql(self):
|
|||
|
|
'''
|
|||
|
|
@name 构建sql查询语句与绑定参数
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@return (sql语句<string>, 绑定参数<tuple>)
|
|||
|
|
'''
|
|||
|
|
fields = self.__OPT_FIELD.build()
|
|||
|
|
join_condition = self.__OPT_JOIN.build()
|
|||
|
|
where_condition, where_params = self.__OPT_WHERE.build()
|
|||
|
|
group_condition, group_params = self.__OPT_GROUP.build()
|
|||
|
|
having_condition, having_params = self.__OPT_HAVING.build()
|
|||
|
|
order_condition, order_params = self.__OPT_ORDER.build()
|
|||
|
|
limit_condition = self.__OPT_LIMIT.build()
|
|||
|
|
|
|||
|
|
# 查询语句
|
|||
|
|
raw_sql = 'SELECT {fields} FROM {table_name}' \
|
|||
|
|
'{join_condition}{where_condition}{group_condition}' \
|
|||
|
|
'{order_condition}{having_condition}{limit_condition}'.format_map({
|
|||
|
|
'fields': fields,
|
|||
|
|
'table_name': self.__build_table_name(),
|
|||
|
|
'join_condition': join_condition,
|
|||
|
|
'where_condition': where_condition,
|
|||
|
|
'group_condition': group_condition,
|
|||
|
|
'having_condition': having_condition,
|
|||
|
|
'order_condition': order_condition,
|
|||
|
|
'limit_condition': limit_condition,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 绑定参数
|
|||
|
|
bind_params = ()
|
|||
|
|
|
|||
|
|
bind_params += where_params
|
|||
|
|
bind_params += group_params
|
|||
|
|
bind_params += having_params
|
|||
|
|
bind_params += order_params
|
|||
|
|
|
|||
|
|
return raw_sql, bind_params
|
|||
|
|
|
|||
|
|
def __build_table_name(self, contain_alias=True):
|
|||
|
|
'''
|
|||
|
|
@name 构建表名
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@param contain_alias<bool> 是否包含别名
|
|||
|
|
@return string
|
|||
|
|
'''
|
|||
|
|
table_name = self.__DB_TABLE if self.__FROM_SUB_QUERY else _add_backtick_for_field(self.__DB_TABLE)
|
|||
|
|
table_alias = ''
|
|||
|
|
|
|||
|
|
if contain_alias:
|
|||
|
|
table_alias = '' if self.__OPT_ALIAS is None else ' AS {}'.format(
|
|||
|
|
_add_backtick_for_field(self.__OPT_ALIAS)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return '{}{}'.format(table_name, table_alias)
|
|||
|
|
|
|||
|
|
def __get_row_key(self, field):
|
|||
|
|
'''
|
|||
|
|
@name 获取字段名对应的字典键名
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@param field<string> 字段名
|
|||
|
|
@return string|None
|
|||
|
|
'''
|
|||
|
|
# 移除字符串两段空白字符
|
|||
|
|
field = str(field).strip()
|
|||
|
|
|
|||
|
|
m = match_row_key_reg.match(field)
|
|||
|
|
|
|||
|
|
# 不符合字段规则
|
|||
|
|
# 返回None
|
|||
|
|
if m is None:
|
|||
|
|
# 检查有无设置别名
|
|||
|
|
m = search_row_key_reg.search(field)
|
|||
|
|
|
|||
|
|
# 设置了别名
|
|||
|
|
if m:
|
|||
|
|
return m.group(1).strip('`')
|
|||
|
|
|
|||
|
|
return field
|
|||
|
|
|
|||
|
|
# 设置了别名时
|
|||
|
|
# 获取别名
|
|||
|
|
if m.group(2) is not None:
|
|||
|
|
return m.group(2).strip('`')
|
|||
|
|
|
|||
|
|
# 获取字段名 不包含前缀表名
|
|||
|
|
return m.group(1).strip('`')
|
|||
|
|
|
|||
|
|
def __is_autocommit(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查是否自动提交事务
|
|||
|
|
@author Zhj<2022-07-18>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return self.__DB.is_autocommit()
|
|||
|
|
|
|||
|
|
def __is_autovacuum(self):
|
|||
|
|
'''
|
|||
|
|
@name 检查是否自动释放空间
|
|||
|
|
@author Zhj<2022-09-27>
|
|||
|
|
@return bool
|
|||
|
|
'''
|
|||
|
|
if self.__DB is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return self.__DB.is_autovacuum()
|
|||
|
|
|
|||
|
|
def __clear(self):
|
|||
|
|
'''
|
|||
|
|
@name 清空查询条件
|
|||
|
|
@author Zhj<2022-07-17>
|
|||
|
|
@return void
|
|||
|
|
'''
|
|||
|
|
self.__PK = None
|
|||
|
|
self.__OPT_ALIAS = None
|
|||
|
|
self.__OPT_FIELD.clear()
|
|||
|
|
self.__OPT_JOIN.clear()
|
|||
|
|
self.__OPT_WHERE.clear()
|
|||
|
|
self.__OPT_GROUP.clear()
|
|||
|
|
self.__OPT_HAVING.clear()
|
|||
|
|
self.__OPT_ORDER.clear()
|
|||
|
|
self.__OPT_LIMIT.clear()
|
|||
|
|
self.__OPT_UPDATE.clear()
|
|||
|
|
self.__OPT_DUPLICATE.clear()
|