# lidb 错误处理指南

## 常见错误类型

### 数据库连接错误

**MySQL连接错误**
- **错误信息**: `RuntimeError: Database configuration error: missing required fields`
- **原因**: 数据库配置中缺少 `user`, `password`, `url`, `db` 等必填字段
- **解决方案**: 检查 `~/.config/lidb/settings.toml` 配置文件，确保包含所有必填字段

**ClickHouse连接错误**
- **错误信息**: `RuntimeError: Failed to execute ClickHouse query`
- **原因**: 集群配置异常或网络问题
- **解决方案**: 验证 `DATABASES.ck` 配置中的 `urls`, `user`, `password` 是否正确

### 数据不存在错误

**空数据返回**
- **错误信息**: `logger.warning("put failed: input data is None/empty")`
- **原因**: `put` 操作输入数据为 `None` 或空 DataFrame
- **解决方案**: 在数据存储前验证数据有效性

**表不存在**
- **错误信息**: `has(tb_name)` 返回 `False`
- **原因**: 指定的表路径不存在
- **解决方案**: 使用 `put()` 先创建表，或验证表名拼写

**分区数据缺失**
- **错误信息**: `logger.warning(f"{file_path}: Deleting empty file.")`
- **原因**: 分区目录中存在大小为0的空文件
- **解决方案**: 清理空文件，重新生成数据

### 配置错误

**配置文件缺失**
- **错误信息**: `FileNotFoundError` 或默认配置被使用
- **原因**: `~/.config/lidb/settings.toml` 文件不存在
- **解决方案**: 创建配置文件，或确保配置路径正确

**配置项缺失**
- **错误信息**: `KeyError` 或 `RuntimeError: Database configuration error`
- **原因**: 必需的配置项未定义
- **解决方案**: 在配置文件中添加缺失的配置项

**路径配置错误**
- **错误信息**: `FileNotFoundError` 或权限错误
- **原因**: `GLOBAL.path` 指向无效目录
- **解决方案**: 验证路径存在且有读写权限

### 依赖解析错误

**表达式依赖冲突**
- **错误信息**: `CompileError` 或 `KeyError`
- **原因**: 表达式引用了不存在的列或表达式
- **解决方案**: 验证所有引用的列名和表达式是否存在

**UDF函数缺失**
- **错误信息**: `AttributeError` 或 `CompileError`
- **原因**: 注册的UDF函数未正确加载
- **解决方案**: 确保UDF函数在 `qdf/udf/` 目录中正确定义

**数据依赖断裂**
- **错误信息**: `ValueError` 或空数据
- **原因**: `Dataset` 的 `depends` 参数引用不存在的数据集
- **解决方案**: 验证所有依赖数据集已正确定义和初始化

### 表达式解析错误

**语法错误**
- **错误信息**: `ParseError`
- **原因**: 表达式字符串不符合语法规则
- **解决方案**: 检查表达式语法，确保符合Lark解析器的要求

**编译错误**
- **错误信息**: `CompileError`
- **原因**: UDF函数参数不匹配或类型错误
- **解决方案**: 验证UDF函数的参数签名

**运行时错误**
- **错误信息**: `PolarsError`
- **原因**: Polars执行表达式时发生错误
- **解决方案**: 检查数据类型和表达式逻辑

## 错误处理策略

### 重试机制

```python
import time
from functools import wraps

def retry_on_failure(max_retries=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except (RuntimeError, ConnectionError) as e:
                    if attempt == max_retries - 1:
                        raise e
                    time.sleep(delay * (2 ** attempt))  # 指数退避
            return None
        return wrapper
    return decorator

@retry_on_failure(max_retries=3, delay=1)
def safe_read_mysql(query, db_conf):
    return read_mysql(query, db_conf)
```

### 降级策略

```python
def get_data_with_fallback(date):
    """获取数据，失败时降级到历史数据"""
    try:
        # 尝试获取最新数据
        return get_realtime_data(date)
    except Exception as e:
        logger.warning(f"Realtime data failed: {e}, falling back to history")
        # 降级到最近可用的历史数据
        prev_date = xcals.shift_tradeday(date, -1)
        return get_historical_data(prev_date)
```

### 默认值处理

```python
def safe_compute(df):
    """安全计算，处理空值和异常"""
    if df is None or df.is_empty():
        # 返回带有默认值的空DataFrame
        return pl.DataFrame({
            "date": pl.Series([], dtype=pl.Utf8),
            "asset": pl.Series([], dtype=pl.Utf8),
            "factor": pl.Series([], dtype=pl.Float64)
        })
    
    return (df
            .with_columns([
                pl.col("price").fill_null(strategy="forward"),
                pl.col("volume").replace({0: None}).fill_null(0)
            ])
            .drop_nulls())
```

### 日志记录

```python
import logair

logger = logair.get_logger(__name__)

def robust_dataset_computation(fn):
    """增强的数据集计算函数"""
    def wrapper(*args, **kwargs):
        try:
            result = fn(*args, **kwargs)
            if result is not None and not result.is_empty():
                logger.info(f"Dataset computation succeeded: {fn.__name__}")
                return result
            else:
                logger.warning(f"Dataset returned empty result: {fn.__name__}")
                return pl.DataFrame()
        except Exception as e:
            logger.error(f"Dataset computation failed: {fn.__name__}\nError: {str(e)}", exc_info=True)
            return pl.DataFrame()
    return wrapper
```

## 调试技巧

### 如何定位错误源

1. **检查日志**：使用 `logger.warning` 和 `logger.error` 记录关键步骤
2. **查看堆栈**：分析异常的完整堆栈跟踪，定位到具体的函数调用
3. **验证输入**：检查函数输入参数是否符合预期类型和格式
4. **逐步调试**：使用 `pdb` 或 IDE 调试器逐步执行代码

### 如何分析错误堆栈

```python
# 示例错误堆栈分析
"""
Traceback (most recent call last):
  File "database.py", line 127, in read_mysql
    db_setting = get_settings().get(db_conf, {})
  File "init.py", line 32, in get_settings
    return Dynaconf(settings_files=[CONFIG_PATH])
  File "<string>", line None
KeyError: 'DATABASES.mysql'
"""

# 分析步骤：
# 1. 最底层：init.py 32行，Dynaconf 读取配置文件
# 2. 中间层：database.py 127行，get_settings().get 调用
# 3. 顶层：调用 read_mysql 时传入的 db_conf 不存在
# 结论：配置文件中缺少 DATABASES.mysql 配置项
```

### 如何使用日志进行调试

```python
# 在关键路径添加日志
def debug_dataset_flow(depend):
    logger.debug(f"Processing dependency with shape: {depend.shape}")
    logger.debug(f"Columns: {depend.columns}")
    
    try:
        result = (depend
                 .select([
                     pl.col("close"),
                     pl.col("volume")
                 ])
                 .filter(pl.col("volume") > 0))
        
        logger.debug(f"Filtered result shape: {result.shape}")
        return result
    except Exception as e:
        logger.error(f"Filter operation failed", exc_info=True)
        raise
```

## 错误代码和解决方案

| 错误代码/异常 | 原因 | 解决方案 |
|-------------|------|---------|
| `RuntimeError: Database configuration error` | 数据库配置缺少必填字段 | 检查 `settings.toml` 文件，确保包含 `user`, `password`, `url`, `db` 字段 |
| `ParseError` | 表达式语法错误 | 检查表达式字符串，确保符合 Lark 解析器的语法规则 |
| `CompileError` | UDF 函数编译失败 | 验证 UDF 函数参数和返回类型是否正确 |
| `PolarsError` | Polars 执行错误 | 检查数据类型和表达式逻辑，避免数据泄露 |
| `KeyError: 'DATABASES.mysql'` | 配置项不存在 | 使用 `get_settings().get('DATABASES.mysql', {})` 提供默认值 |
| `Empty data for date` | 数据计算返回空 | 检查数据源是否存在，验证过滤条件是否过严 |

## 错误处理代码示例

```python
from dataclasses import dataclass
from typing import Optional
import polars as pl
import logging

@dataclass
class ErrorResult:
    success: bool
    data: Optional[pl.DataFrame]
    error_type: Optional[str]
    error_message: Optional[str]
    stack_trace: Optional[str]

class RobustDataset:
    """具有错误处理能力的数据集类"""
    
    def __init__(self, fn, tb, update_time=""):
        self.fn = fn
        self.tb = tb
        self.update_time = update_time
        self.logger = logging.getLogger(__name__)
    
    def get_value_safe(self, date, **kwargs):
        """安全获取数据值"""
        try:
            # 参数验证
            if not isinstance(date, str) or len(date) != 10:
                raise ValueError(f"Invalid date format: {date}")
            
            # 执行原始函数
            result = self.fn(date, **kwargs)
            
            # 结果验证
            if result is None:
                self.logger.warning(f"Function returned None for {date}")
                return ErrorResult(
                    success=False,
                    data=None,
                    error_type="ValueError",
                    error_message="Function returned None",
                    stack_trace=None
                )
            
            if result.is_empty():
                self.logger.info(f"Function returned empty DataFrame for {date}")
                return ErrorResult(
                    success=True,
                    data=result,
                    error_type=None,
                    error_message=None,
                    stack_trace=None
                )
            
            # 类型验证
            required_cols = ["date", "asset"]
            missing_cols = [col for col in required_cols if col not in result.columns]
            if missing_cols:
                self.logger.warning(f"Missing required columns: {missing_cols}")
                # 尝试修复
                for col in missing_cols:
                    if col == "date":
                        result = result.with_columns(pl.lit(date).alias("date"))
                    elif col == "asset" and "code" in result.columns:
                        result = result.rename({"code": "asset"})
            
            return ErrorResult(
                success=True,
                data=result,
                error_type=None,
                error_message=None,
                stack_trace=None
            )
            
        except Exception as e:
            self.logger.error(f"Failed to get value for {date}: {str(e)}", exc_info=True)
            return ErrorResult(
                success=False,
                data=None,
                error_type=type(e).__name__,
                error_message=str(e),
                stack_trace=str(e.__traceback__)
            )

# 使用示例
def sample_data_function(date):
    # 模拟可能失败的数据获取
    if date < "2023-01-01":
        raise ValueError("Data not available for dates before 2023")
    return pl.DataFrame({
        "date": [date],
        "asset": ["AAPL"],
        "price": [150.0]
    })

dataset = RobustDataset(sample_data_function, "test_table")
result = dataset.get_value_safe("2022-12-01")

if result.success:
    print("Data retrieved successfully")
    if result.data is not None:
        print(result.data)
else:
    print(f"Error: {result.error_type} - {result.error_message}")
```