erpnext-errors-database

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ERPNext Database - Error Handling

ERPNext 数据库 - 错误处理

This skill covers error handling patterns for database operations. For syntax, see
erpnext-database
.
Version: v14/v15/v16 compatible

本技能涵盖数据库操作的错误处理模式。语法相关内容请参考
erpnext-database
版本:兼容v14/v15/v16

Database Exception Types

数据库异常类型

┌─────────────────────────────────────────────────────────────────────┐
│ FRAPPE DATABASE EXCEPTIONS                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ frappe.DoesNotExistError                                            │
│   └─► Document not found (get_doc, get_value with strict)           │
│                                                                     │
│ frappe.DuplicateEntryError                                          │
│   └─► Unique constraint violation (insert, rename)                  │
│                                                                     │
│ frappe.LinkExistsError                                              │
│   └─► Cannot delete - linked documents exist                        │
│                                                                     │
│ frappe.ValidationError                                              │
│   └─► General validation failure                                    │
│                                                                     │
│ frappe.TimestampMismatchError                                       │
│   └─► Concurrent edit detected (modified since load)                │
│                                                                     │
│ frappe.db.InternalError                                             │
│   └─► Database-level error (deadlock, connection lost)              │
│                                                                     │
│ frappe.QueryTimeoutError (v15+)                                     │
│   └─► Query exceeded timeout limit                                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ FRAPPE DATABASE EXCEPTIONS                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│ frappe.DoesNotExistError                                            │
│   └─► 文档未找到(使用get_doc、带strict参数的get_value时触发)           │
│                                                                     │
│ frappe.DuplicateEntryError                                          │
│   └─► 唯一约束冲突(插入、重命名操作时触发)                  │
│                                                                     │
│ frappe.LinkExistsError                                              │
│   └─► 无法删除 - 存在关联文档                        │
│                                                                     │
│ frappe.ValidationError                                              │
│   └─► 通用验证失败                                    │
│                                                                     │
│ frappe.TimestampMismatchError                                       │
│   └─► 检测到并发编辑(加载后已被修改)                │
│                                                                     │
│ frappe.db.InternalError                                             │
│   └─► 数据库级错误(死锁、连接丢失)              │
│                                                                     │
│ frappe.QueryTimeoutError (v15+)                                     │
│   └─► 查询超出超时限制                                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Main Decision: Error Handling by Operation

核心决策:按操作类型处理错误

┌─────────────────────────────────────────────────────────────────────────┐
│ WHAT DATABASE OPERATION?                                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ► frappe.get_doc() / frappe.get_cached_doc()                            │
│   └─► Can raise DoesNotExistError                                       │
│   └─► Check with frappe.db.exists() first OR catch exception            │
│                                                                         │
│ ► doc.insert() / frappe.new_doc().insert()                              │
│   └─► Can raise DuplicateEntryError (unique constraints)                │
│   └─► Can raise ValidationError (mandatory fields, custom validation)   │
│                                                                         │
│ ► doc.save()                                                            │
│   └─► Can raise ValidationError                                         │
│   └─► Can raise TimestampMismatchError (concurrent edit)                │
│                                                                         │
│ ► doc.delete() / frappe.delete_doc()                                    │
│   └─► Can raise LinkExistsError (linked documents)                      │
│   └─► Use force=True to ignore links (careful!)                         │
│                                                                         │
│ ► frappe.db.sql() / frappe.qb                                           │
│   └─► Can raise InternalError (syntax, deadlock, connection)            │
│   └─► Always use parameterized queries                                  │
│                                                                         │
│ ► frappe.db.set_value() / doc.db_set()                                  │
│   └─► Silently fails if record doesn't exist                            │
│   └─► No validation triggered                                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│ 执行的数据库操作类型?                                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ► frappe.get_doc() / frappe.get_cached_doc()                            │
│   └─► 可能抛出DoesNotExistError                                       │
│   └─► 先使用frappe.db.exists()检查 或 捕获异常            │
│                                                                         │
│ ► doc.insert() / frappe.new_doc().insert()                              │
│   └─► 可能抛出DuplicateEntryError(违反唯一约束)                │
│   └─► 可能抛出ValidationError(必填字段缺失、自定义验证失败)   │
│                                                                         │
│ ► doc.save()                                                            │
│   └─► 可能抛出ValidationError                                         │
│   └─► 可能抛出TimestampMismatchError(并发编辑)                │
│                                                                         │
│ ► doc.delete() / frappe.delete_doc()                                    │
│   └─► 可能抛出LinkExistsError(存在关联文档)                      │
│   └─► 使用force=True参数忽略关联(需谨慎使用!)                         │
│                                                                         │
│ ► frappe.db.sql() / frappe.qb                                           │
│   └─► 可能抛出InternalError(语法错误、死锁、连接问题)            │
│   └─► 始终使用参数化查询                                  │
│                                                                         │
│ ► frappe.db.set_value() / doc.db_set()                                  │
│   └─► 若记录不存在则静默失败                            │
│   └─► 不会触发任何验证                                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Error Handling Patterns

错误处理模式

Pattern 1: Safe Document Fetch

模式1:安全获取文档

python
undefined
python
undefined

Option A: Check first (preferred for expected missing docs)

选项A:先检查(适用于预期可能缺失的文档)

if frappe.db.exists("Customer", customer_name): customer = frappe.get_doc("Customer", customer_name) else: frappe.throw(_("Customer '{0}' not found").format(customer_name))
if frappe.db.exists("Customer", customer_name): customer = frappe.get_doc("Customer", customer_name) else: frappe.throw(_("客户 '{0}' 不存在").format(customer_name))

Option B: Try/except (preferred when doc usually exists)

选项B:Try/Except捕获(适用于文档通常存在的场景)

try: customer = frappe.get_doc("Customer", customer_name) except frappe.DoesNotExistError: frappe.throw(_("Customer '{0}' not found").format(customer_name))
try: customer = frappe.get_doc("Customer", customer_name) except frappe.DoesNotExistError: frappe.throw(_("客户 '{0}' 不存在").format(customer_name))

Option C: Get with default (for optional lookups)

选项C:带默认值获取(适用于可选查询)

customer = frappe.db.get_value("Customer", customer_name, "*", as_dict=True) if not customer: # Handle missing - no error raised customer = {"customer_name": "Unknown", "credit_limit": 0}
undefined
customer = frappe.db.get_value("Customer", customer_name, "*", as_dict=True) if not customer: # 处理缺失情况 - 不会抛出错误 customer = {"customer_name": "未知", "credit_limit": 0}
undefined

Pattern 2: Safe Document Insert

模式2:安全插入文档

python
def create_customer(data):
    """Create customer with duplicate handling."""
    try:
        doc = frappe.get_doc({
            "doctype": "Customer",
            "customer_name": data.get("name"),
            "customer_type": data.get("type", "Company")
        })
        doc.insert()
        return {"success": True, "name": doc.name}
        
    except frappe.DuplicateEntryError:
        # Already exists - return existing
        existing = frappe.db.get_value("Customer", {"customer_name": data.get("name")})
        return {"success": True, "name": existing, "existing": True}
        
    except frappe.ValidationError as e:
        return {"success": False, "error": str(e)}
python
def create_customer(data):
    """创建客户并处理重复情况。"""
    try:
        doc = frappe.get_doc({
            "doctype": "Customer",
            "customer_name": data.get("name"),
            "customer_type": data.get("type", "Company")
        })
        doc.insert()
        return {"success": True, "name": doc.name}
        
    except frappe.DuplicateEntryError:
        # 已存在 - 返回现有记录
        existing = frappe.db.get_value("Customer", {"customer_name": data.get("name")})
        return {"success": True, "name": existing, "existing": True}
        
    except frappe.ValidationError as e:
        return {"success": False, "error": str(e)}

Pattern 3: Safe Document Delete

模式3:安全删除文档

python
def delete_customer(customer_name):
    """Delete customer with link handling."""
    if not frappe.db.exists("Customer", customer_name):
        frappe.throw(_("Customer '{0}' not found").format(customer_name))
    
    try:
        frappe.delete_doc("Customer", customer_name)
        return {"success": True}
        
    except frappe.LinkExistsError as e:
        # Get linked documents for user info
        linked = get_linked_documents("Customer", customer_name)
        frappe.throw(
            _("Cannot delete customer. Linked documents exist:<br>{0}").format(
                "<br>".join([f"• {l['doctype']}: {l['name']}" for l in linked[:10]])
            )
        )
python
def delete_customer(customer_name):
    """删除客户并处理关联文档情况。"""
    if not frappe.db.exists("Customer", customer_name):
        frappe.throw(_("客户 '{0}' 不存在").format(customer_name))
    
    try:
        frappe.delete_doc("Customer", customer_name)
        return {"success": True}
        
    except frappe.LinkExistsError as e:
        # 获取关联文档信息供用户查看
        linked = get_linked_documents("Customer", customer_name)
        frappe.throw(
            _("无法删除客户。存在以下关联文档:<br>{0}").format(
                "<br>".join([f"• {l['doctype']}: {l['name']}" for l in linked[:10]])
            )
        )

Pattern 4: Concurrent Edit Handling

模式4:并发编辑处理

python
def update_document(doctype, name, updates):
    """Update with concurrent edit detection."""
    try:
        doc = frappe.get_doc(doctype, name)
        doc.update(updates)
        doc.save()
        return {"success": True}
        
    except frappe.TimestampMismatchError:
        # Document was modified by another user
        frappe.throw(
            _("This document was modified by another user. Please refresh and try again."),
            title=_("Concurrent Edit Detected")
        )
    except frappe.DoesNotExistError:
        frappe.throw(_("Document not found"))
python
def update_document(doctype, name, updates):
    """更新文档并检测并发编辑。"""
    try:
        doc = frappe.get_doc(doctype, name)
        doc.update(updates)
        doc.save()
        return {"success": True}
        
    except frappe.TimestampMismatchError:
        # 文档已被其他用户修改
        frappe.throw(
            _("该文档已被其他用户修改,请刷新后重试。"),
            title=_("检测到并发编辑")
        )
    except frappe.DoesNotExistError:
        frappe.throw(_("文档未找到"))

Pattern 5: Batch Operations with Error Isolation

模式5:批量操作的错误隔离

python
def bulk_update_items(items_data):
    """Bulk update with per-item error handling."""
    results = {"success": [], "failed": []}
    
    for item_data in items_data:
        item_code = item_data.get("item_code")
        
        try:
            if not frappe.db.exists("Item", item_code):
                results["failed"].append({
                    "item": item_code,
                    "error": "Item not found"
                })
                continue
            
            doc = frappe.get_doc("Item", item_code)
            doc.update(item_data)
            doc.save()
            results["success"].append(item_code)
            
        except frappe.ValidationError as e:
            results["failed"].append({
                "item": item_code,
                "error": str(e)
            })
        except Exception as e:
            frappe.log_error(frappe.get_traceback(), f"Bulk update error: {item_code}")
            results["failed"].append({
                "item": item_code,
                "error": "Unexpected error"
            })
    
    return results
python
def bulk_update_items(items_data):
    """批量更新并处理单条记录的错误。"""
    results = {"success": [], "failed": []}
    
    for item_data in items_data:
        item_code = item_data.get("item_code")
        
        try:
            if not frappe.db.exists("Item", item_code):
                results["failed"].append({
                    "item": item_code,
                    "error": "商品不存在"
                })
                continue
            
            doc = frappe.get_doc("Item", item_code)
            doc.update(item_data)
            doc.save()
            results["success"].append(item_code)
            
        except frappe.ValidationError as e:
            results["failed"].append({
                "item": item_code,
                "error": str(e)
            })
        except Exception as e:
            frappe.log_error(frappe.get_traceback(), f"批量更新错误: {item_code}")
            results["failed"].append({
                "item": item_code,
                "error": "意外错误"
            })
    
    return results

Pattern 6: Safe SQL Query

模式6:安全SQL查询

python
def get_sales_report(customer, from_date, to_date):
    """Safe SQL query with error handling."""
    try:
        # ALWAYS use parameterized queries
        result = frappe.db.sql("""
            SELECT 
                customer,
                SUM(grand_total) as total,
                COUNT(*) as count
            FROM `tabSales Invoice`
            WHERE customer = %(customer)s
            AND posting_date BETWEEN %(from_date)s AND %(to_date)s
            AND docstatus = 1
            GROUP BY customer
        """, {
            "customer": customer,
            "from_date": from_date,
            "to_date": to_date
        }, as_dict=True)
        
        return result[0] if result else {"total": 0, "count": 0}
        
    except frappe.db.InternalError as e:
        frappe.log_error(frappe.get_traceback(), "Sales Report Query Error")
        frappe.throw(_("Database error. Please try again or contact support."))
See:
references/patterns.md
for more error handling patterns.

python
def get_sales_report(customer, from_date, to_date):
    """带错误处理的安全SQL查询。"""
    try:
        # 始终使用参数化查询
        result = frappe.db.sql("""
            SELECT 
                customer,
                SUM(grand_total) as total,
                COUNT(*) as count
            FROM `tabSales Invoice`
            WHERE customer = %(customer)s
            AND posting_date BETWEEN %(from_date)s AND %(to_date)s
            AND docstatus = 1
            GROUP BY customer
        """, {
            "customer": customer,
            "from_date": from_date,
            "to_date": to_date
        }, as_dict=True)
        
        return result[0] if result else {"total": 0, "count": 0}
        
    except frappe.db.InternalError as e:
        frappe.log_error(frappe.get_traceback(), "销售报表查询错误")
        frappe.throw(_("数据库错误,请重试或联系支持人员。"))
参考:更多错误处理模式请查看
references/patterns.md

Transaction Handling

事务处理

Automatic Transaction Management

自动事务管理

python
undefined
python
undefined

Frappe wraps each request in a transaction

Frappe会为每个请求自动包裹事务

On success: auto-commit

成功时:自动提交

On exception: auto-rollback

发生异常时:自动回滚

def validate(self): # All changes are in ONE transaction self.calculate_totals() frappe.db.set_value("Counter", "main", "count", 100)
if error_condition:
    frappe.throw("Error")  # EVERYTHING rolls back
undefined
def validate(self): # 所有修改都在同一个事务中 self.calculate_totals() frappe.db.set_value("Counter", "main", "count", 100)
if error_condition:
    frappe.throw("错误")  # 所有操作都会回滚
undefined

Manual Savepoints (Advanced)

手动保存点(高级用法)

python
def complex_operation():
    """Use savepoints for partial rollback."""
    # Create savepoint
    frappe.db.savepoint("before_risky_op")
    
    try:
        risky_database_operation()
    except Exception:
        # Rollback only to savepoint
        frappe.db.rollback(save_point="before_risky_op")
        frappe.log_error(frappe.get_traceback(), "Risky Op Failed")
        # Continue with alternative approach
        safe_alternative_operation()
python
def complex_operation():
    """使用保存点实现部分回滚。"""
    # 创建保存点
    frappe.db.savepoint("before_risky_op")
    
    try:
        risky_database_operation()
    except Exception:
        # 仅回滚到保存点
        frappe.db.rollback(save_point="before_risky_op")
        frappe.log_error(frappe.get_traceback(), "高风险操作失败")
        # 继续执行替代方案
        safe_alternative_operation()

Scheduler/Background Jobs

调度器/后台任务

python
def background_task():
    """Background jobs need explicit commit."""
    try:
        for record in records:
            process_record(record)
        
        # REQUIRED in background jobs
        frappe.db.commit()
        
    except Exception:
        frappe.db.rollback()
        frappe.log_error(frappe.get_traceback(), "Background Task Error")

python
def background_task():
    """后台任务需要显式提交。"""
    try:
        for record in records:
            process_record(record)
        
        # 后台任务中必须显式提交
        frappe.db.commit()
        
    except Exception:
        frappe.db.rollback()
        frappe.log_error(frappe.get_traceback(), "后台任务错误")

Critical Rules

关键规则

✅ ALWAYS

✅ 必须遵守

  1. Check existence before get_doc - Or catch DoesNotExistError
  2. Use parameterized SQL queries - Never string formatting
  3. Handle DuplicateEntryError on insert - Unique constraints
  4. Commit in scheduler/background jobs - No auto-commit
  5. Log database errors with context - Include query/doc info
  6. Use db.exists() for existence checks - Not try/except get_doc
  1. 获取文档前先检查存在性 - 或者捕获DoesNotExistError
  2. 使用参数化SQL查询 - 绝不使用字符串拼接
  3. 插入时处理DuplicateEntryError - 处理唯一约束冲突
  4. 调度器/后台任务中显式提交 - 无自动提交机制
  5. 记录带上下文的数据库错误 - 包含查询/文档信息
  6. 使用db.exists()检查存在性 - 而非通过try/except捕获get_doc的异常

❌ NEVER

❌ 严禁操作

  1. Don't use string formatting in SQL - SQL injection risk
  2. Don't commit in controller hooks - Breaks transaction
  3. Don't ignore DoesNotExistError silently - Handle or log
  4. Don't assume db.set_value() succeeded - No error on missing doc
  5. Don't catch generic Exception for database ops - Catch specific types

  1. SQL查询中使用字符串拼接 - 存在SQL注入风险
  2. 在控制器钩子中提交事务 - 会破坏事务逻辑
  3. 静默忽略DoesNotExistError - 必须处理或记录
  4. 假设db.set_value()一定成功 - 记录不存在时会静默失败
  5. 数据库操作中捕获通用Exception - 应捕获特定异常类型

Quick Reference: Exception Handling

快速参考:异常处理

python
undefined
python
undefined

DoesNotExistError - Document not found

DoesNotExistError - 文档未找到

try: doc = frappe.get_doc("Customer", name) except frappe.DoesNotExistError: frappe.throw(_("Customer not found"))
try: doc = frappe.get_doc("Customer", name) except frappe.DoesNotExistError: frappe.throw(_("客户未找到"))

DuplicateEntryError - Unique constraint violation

DuplicateEntryError - 违反唯一约束

try: doc.insert() except frappe.DuplicateEntryError: # Handle duplicate
try: doc.insert() except frappe.DuplicateEntryError: # 处理重复情况

LinkExistsError - Cannot delete linked document

LinkExistsError - 无法删除存在关联的文档

try: frappe.delete_doc("Customer", name) except frappe.LinkExistsError: frappe.throw(_("Cannot delete - linked documents exist"))
try: frappe.delete_doc("Customer", name) except frappe.LinkExistsError: frappe.throw(_("无法删除 - 存在关联文档"))

TimestampMismatchError - Concurrent edit

TimestampMismatchError - 并发编辑

try: doc.save() except frappe.TimestampMismatchError: frappe.throw(_("Document was modified. Please refresh."))
try: doc.save() except frappe.TimestampMismatchError: frappe.throw(_("文档已被修改,请刷新后重试。"))

InternalError - Database-level error

InternalError - 数据库级错误

try: frappe.db.sql(query) except frappe.db.InternalError: frappe.log_error(frappe.get_traceback(), "Database Error") frappe.throw(_("Database error occurred"))

---
try: frappe.db.sql(query) except frappe.db.InternalError: frappe.log_error(frappe.get_traceback(), "数据库错误") frappe.throw(_("发生数据库错误"))

---

Reference Files

参考文件

FileContents
references/patterns.md
Complete error handling patterns
references/examples.md
Full working examples
references/anti-patterns.md
Common mistakes to avoid

文件内容
references/patterns.md
完整的错误处理模式
references/examples.md
完整的可运行示例
references/anti-patterns.md
需避免的常见错误

See Also

另请参阅

  • erpnext-database
    - Database operations syntax
  • erpnext-errors-controllers
    - Controller error handling
  • erpnext-errors-serverscripts
    - Server Script error handling
  • erpnext-permissions
    - Permission patterns
  • erpnext-database
    - 数据库操作语法
  • erpnext-errors-controllers
    - 控制器错误处理
  • erpnext-errors-serverscripts
    - 服务器脚本错误处理
  • erpnext-permissions
    - 权限模式