mxivideo/python_core/database/db.py

518 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import uuid
from typing import Dict, List, Any, Optional, Union
from datetime import datetime
from python_core.kv import kv
from python_core.utils.logger import setup_logger
logger = setup_logger(__name__)
class Db:
"""
基于KV存储的数据库类
提供基础的CRUD操作、索引、查询等功能
"""
def __init__(self, key: str):
"""
初始化数据库
Args:
key: 数据库的唯一标识符用作KV存储的前缀
"""
self.kv = kv
self.key = key
self.table_prefix = f"db:{key}:table:"
self.index_prefix = f"db:{key}:index:"
self.meta_prefix = f"db:{key}:meta:"
# 初始化数据库元数据
self._init_metadata()
def _init_metadata(self):
"""初始化数据库元数据"""
try:
meta_key = f"{self.meta_prefix}info"
existing_meta = self.kv.get(meta_key)
if not existing_meta:
# 创建新的数据库元数据
metadata = {
"created_at": datetime.now().isoformat(),
"version": "1.0.0",
"tables": [],
"indexes": {}
}
self.kv.set(meta_key, json.dumps(metadata))
logger.info(f"Initialized new database: {self.key}")
else:
logger.info(f"Connected to existing database: {self.key}")
except Exception as e:
logger.error(f"Failed to initialize database metadata: {e}")
raise e
def _get_metadata(self) -> Dict[str, Any]:
"""获取数据库元数据"""
try:
meta_key = f"{self.meta_prefix}info"
logger.debug(f"Getting metadata with key: {meta_key}")
meta_str = self.kv.get(meta_key)
logger.debug(f"Raw metadata string: {meta_str}")
if meta_str:
parsed = json.loads(meta_str)
logger.debug(f"Parsed metadata: {parsed}")
return parsed
logger.debug("No metadata found, returning empty dict")
return {}
except Exception as e:
logger.error(f"Failed to get database metadata: {e}")
import traceback
traceback.print_exc()
return {}
def _update_metadata(self, metadata: Dict[str, Any]):
"""更新数据库元数据"""
try:
meta_key = f"{self.meta_prefix}info"
logger.debug(f"Updating metadata with key: {meta_key}")
logger.debug(f"Metadata content: {metadata}")
result = self.kv.set(meta_key, json.dumps(metadata))
logger.debug(f"Update result: {result}")
# 添加短暂延迟确保KV同步
import time
time.sleep(0.1)
except Exception as e:
logger.error(f"Failed to update database metadata: {e}")
raise e
def create_table(self, table_name: str, schema: Dict[str, str] = None) -> bool:
"""
创建表
Args:
table_name: 表名
schema: 表结构定义 (字段名: 类型)
Returns:
创建成功返回True
"""
try:
metadata = self._get_metadata()
if table_name in metadata.get("tables", []):
logger.warning(f"Table '{table_name}' already exists")
return False
# 创建表元数据
table_meta = {
"name": table_name,
"created_at": datetime.now().isoformat(),
"schema": schema or {},
"record_count": 0,
"indexes": []
}
# 保存表元数据
table_meta_key = f"{self.meta_prefix}table:{table_name}"
self.kv.set(table_meta_key, json.dumps(table_meta))
# 更新数据库元数据
metadata.setdefault("tables", []).append(table_name)
self._update_metadata(metadata)
logger.info(f"Created table: {table_name}")
return True
except Exception as e:
logger.error(f"Failed to create table '{table_name}': {e}")
raise e
def drop_table(self, table_name: str) -> bool:
"""
删除表
Args:
table_name: 表名
Returns:
删除成功返回True
"""
try:
metadata = self._get_metadata()
if table_name not in metadata.get("tables", []):
logger.warning(f"Table '{table_name}' does not exist")
return False
# 获取表中所有记录的键
record_keys = self._get_all_record_keys(table_name)
# 删除所有记录
if record_keys:
self.kv.removes(record_keys)
# 删除表元数据
table_meta_key = f"{self.meta_prefix}table:{table_name}"
self.kv.remove(table_meta_key)
# 删除相关索引
self._drop_table_indexes(table_name)
# 更新数据库元数据
metadata["tables"] = [t for t in metadata.get("tables", []) if t != table_name]
self._update_metadata(metadata)
logger.info(f"Dropped table: {table_name}")
return True
except Exception as e:
logger.error(f"Failed to drop table '{table_name}': {e}")
raise e
def insert(self, table_name: str, data: Dict[str, Any], record_id: str = None) -> str:
"""
插入记录
Args:
table_name: 表名
data: 记录数据
record_id: 记录ID如果不提供则自动生成
Returns:
记录ID
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
# 生成记录ID
if not record_id:
import uuid
record_id = str(uuid.uuid4())
# 添加元数据
record = {
"id": record_id,
"data": data,
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
}
# 保存记录
record_key = f"{self.table_prefix}{table_name}:record:{record_id}"
self.kv.set(record_key, json.dumps(record))
# 更新表统计
self._update_table_stats(table_name, record_count_delta=1)
# 更新索引
self._update_indexes(table_name, record_id, data)
logger.debug(f"Inserted record {record_id} into table {table_name}")
return record_id
except Exception as e:
logger.error(f"Failed to insert record into table '{table_name}': {e}")
raise e
def get(self, table_name: str, record_id: str) -> Optional[Dict[str, Any]]:
"""
获取单条记录
Args:
table_name: 表名
record_id: 记录ID
Returns:
记录数据如果不存在返回None
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
record_key = f"{self.table_prefix}{table_name}:record:{record_id}"
record_str = self.kv.get(record_key)
if record_str:
record = json.loads(record_str)
return record
return None
except Exception as e:
logger.error(f"Failed to get record {record_id} from table '{table_name}': {e}")
raise e
def update(self, table_name: str, record_id: str, data: Dict[str, Any]) -> bool:
"""
更新记录
Args:
table_name: 表名
record_id: 记录ID
data: 新的数据
Returns:
更新成功返回True
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
# 获取现有记录
existing_record = self.get(table_name, record_id)
if not existing_record:
logger.warning(f"Record {record_id} not found in table {table_name}")
return False
# 更新记录
updated_record = {
"id": record_id,
"data": data,
"created_at": existing_record.get("created_at"),
"updated_at": datetime.now().isoformat()
}
# 保存更新后的记录
record_key = f"{self.table_prefix}{table_name}:record:{record_id}"
self.kv.set(record_key, json.dumps(updated_record))
# 更新索引
self._update_indexes(table_name, record_id, data, existing_record.get("data"))
logger.debug(f"Updated record {record_id} in table {table_name}")
return True
except Exception as e:
logger.error(f"Failed to update record {record_id} in table '{table_name}': {e}")
raise e
def delete(self, table_name: str, record_id: str) -> bool:
"""
删除记录
Args:
table_name: 表名
record_id: 记录ID
Returns:
删除成功返回True
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
# 获取记录以便清理索引
existing_record = self.get(table_name, record_id)
if not existing_record:
logger.warning(f"Record {record_id} not found in table {table_name}")
return False
# 删除记录
record_key = f"{self.table_prefix}{table_name}:record:{record_id}"
self.kv.remove(record_key)
# 更新表统计
self._update_table_stats(table_name, record_count_delta=-1)
# 清理索引
self._remove_from_indexes(table_name, record_id, existing_record.get("data"))
logger.debug(f"Deleted record {record_id} from table {table_name}")
return True
except Exception as e:
logger.error(f"Failed to delete record {record_id} from table '{table_name}': {e}")
raise e
def find_all(self, table_name: str, limit: int = 100) -> List[Dict[str, Any]]:
"""
获取表中所有记录
Args:
table_name: 表名
limit: 最大返回记录数
Returns:
记录列表
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
# 获取所有记录键
record_keys = self._get_all_record_keys(table_name, limit)
if not record_keys:
return []
# 批量获取记录
records_data = self.kv.gets(record_keys)
records = []
for key, record_str in records_data.items():
if record_str:
try:
record = json.loads(record_str)
records.append(record)
except json.JSONDecodeError:
logger.warning(f"Failed to parse record: {key}")
return records
except Exception as e:
logger.error(f"Failed to find all records in table '{table_name}': {e}")
raise e
def find_by_field(self, table_name: str, field: str, value: Any, limit: int = 100) -> List[Dict[str, Any]]:
"""
根据字段值查找记录
Args:
table_name: 表名
field: 字段名
value: 字段值
limit: 最大返回记录数
Returns:
匹配的记录列表
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
# 简单的线性搜索(后续可以优化为索引搜索)
all_records = self.find_all(table_name, limit * 2) # 获取更多记录以便过滤
matching_records = []
for record in all_records:
if len(matching_records) >= limit:
break
record_data = record.get("data", {})
if record_data.get(field) == value:
matching_records.append(record)
return matching_records
except Exception as e:
logger.error(f"Failed to find records by field in table '{table_name}': {e}")
raise e
def count(self, table_name: str) -> int:
"""
获取表中记录数量
Args:
table_name: 表名
Returns:
记录数量
"""
try:
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
table_meta = self._get_table_metadata(table_name)
return table_meta.get("record_count", 0)
except Exception as e:
logger.error(f"Failed to count records in table '{table_name}': {e}")
raise e
# 辅助方法
def _table_exists(self, table_name: str) -> bool:
"""检查表是否存在"""
metadata = self._get_metadata()
return table_name in metadata.get("tables", [])
def _get_table_metadata(self, table_name: str) -> Dict[str, Any]:
"""获取表元数据"""
table_meta_key = f"{self.meta_prefix}table:{table_name}"
meta_str = self.kv.get(table_meta_key)
if meta_str:
return json.loads(meta_str)
return {}
def _update_table_stats(self, table_name: str, record_count_delta: int = 0):
"""更新表统计信息"""
try:
table_meta = self._get_table_metadata(table_name)
table_meta["record_count"] = table_meta.get("record_count", 0) + record_count_delta
table_meta_key = f"{self.meta_prefix}table:{table_name}"
self.kv.set(table_meta_key, json.dumps(table_meta))
except Exception as e:
logger.error(f"Failed to update table stats for '{table_name}': {e}")
def _get_all_record_keys(self, table_name: str, limit: int = 1000) -> List[str]:
"""获取表中所有记录的键"""
try:
prefix = f"{self.table_prefix}{table_name}:record:"
# 使用KV的list_keys功能
result = self.kv.list_keys(prefix=prefix, limit=limit)
if result and "result" in result:
keys = []
for key_info in result["result"]:
if isinstance(key_info, dict):
keys.append(key_info.get("name"))
else:
keys.append(str(key_info))
return keys
return []
except Exception as e:
logger.error(f"Failed to get record keys for table '{table_name}': {e}")
return []
def _update_indexes(self, table_name: str, record_id: str, data: Dict[str, Any], old_data: Dict[str, Any] = None):
"""更新索引(简单实现,后续可扩展)"""
# 这里可以实现索引更新逻辑
# 目前为简单实现,不做复杂索引
pass
def _remove_from_indexes(self, table_name: str, record_id: str, data: Dict[str, Any]):
"""从索引中移除记录"""
# 这里可以实现索引清理逻辑
pass
def _drop_table_indexes(self, table_name: str):
"""删除表的所有索引"""
# 这里可以实现索引删除逻辑
pass
# 实用方法
def list_tables(self) -> List[str]:
"""列出所有表"""
metadata = self._get_metadata()
return metadata.get("tables", [])
def table_info(self, table_name: str) -> Dict[str, Any]:
"""获取表信息"""
if not self._table_exists(table_name):
raise ValueError(f"Table '{table_name}' does not exist")
return self._get_table_metadata(table_name)
def database_info(self) -> Dict[str, Any]:
"""获取数据库信息"""
metadata = self._get_metadata()
# 添加表的详细信息
tables_info = {}
for table_name in metadata.get("tables", []):
tables_info[table_name] = self._get_table_metadata(table_name)
return {
"database_key": self.key,
"metadata": metadata,
"tables": tables_info
}