241 lines
9.8 KiB
Python
241 lines
9.8 KiB
Python
"""
|
||
Cypher 错误修复模块
|
||
|
||
功能:修复常见的 Cypher 语法错误和格式问题
|
||
"""
|
||
|
||
import re
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def parse_cypher_error(error_message: str, cypher: str) -> dict:
|
||
"""
|
||
解析Cypher错误信息,提取错误类型、位置等详细信息
|
||
"""
|
||
error_info = {
|
||
"error_type": "unknown",
|
||
"error_message": error_message,
|
||
"error_position": None,
|
||
"suggested_fix": None
|
||
}
|
||
|
||
error_lower = error_message.lower()
|
||
|
||
# ========== 识别错误类型 ==========
|
||
if "unexpected" in error_lower or "expected" in error_lower:
|
||
error_info["error_type"] = "syntax_unexpected"
|
||
elif "invalid" in error_lower:
|
||
error_info["error_type"] = "syntax_invalid"
|
||
elif "missing" in error_lower:
|
||
error_info["error_type"] = "syntax_missing"
|
||
elif "cannot" in error_lower or "can't" in error_lower:
|
||
error_info["error_type"] = "semantic_error"
|
||
elif "unknown" in error_lower or "not found" in error_lower:
|
||
error_info["error_type"] = "schema_error"
|
||
elif "syntax" in error_lower:
|
||
error_info["error_type"] = "syntax_error"
|
||
|
||
# ========== 提取错误位置(如果错误信息中包含位置信息) ==========
|
||
# Neo4j错误信息可能包含位置,如 "line 1, column 10"
|
||
position_match = re.search(r'line\s+(\d+)[,\s]+column\s+(\d+)', error_message, re.IGNORECASE)
|
||
if position_match:
|
||
line_num = int(position_match.group(1))
|
||
col_num = int(position_match.group(2))
|
||
error_info["error_position"] = {"line": line_num, "column": col_num}
|
||
|
||
# 提取错误位置的上下文
|
||
lines = cypher.split('\n')
|
||
if line_num <= len(lines):
|
||
error_line = lines[line_num - 1]
|
||
error_info["error_context"] = error_line
|
||
if col_num <= len(error_line):
|
||
# 标记错误位置
|
||
marker = ' ' * (col_num - 1) + '^'
|
||
error_info["error_marker"] = marker
|
||
|
||
# ========== 提取错误相关的关键词 ==========
|
||
# 提取错误信息中提到的标签、关系、关键字等
|
||
keywords = []
|
||
# 匹配可能的标签名(大写字母开头的单词)
|
||
label_matches = re.findall(r'\b([A-Z][a-zA-Z0-9_]+)\b', error_message)
|
||
keywords.extend(label_matches)
|
||
# 匹配引号中的内容
|
||
quoted_matches = re.findall(r'["\']([^"\']+)["\']', error_message)
|
||
keywords.extend(quoted_matches)
|
||
error_info["keywords"] = list(set(keywords))
|
||
|
||
return error_info
|
||
|
||
|
||
def force_undirected_relationships(cypher: str) -> str:
|
||
"""
|
||
强制将所有有方向的关系转换为无方向关系
|
||
"""
|
||
if not cypher:
|
||
return cypher
|
||
|
||
# 替换所有有方向的关系为无方向关系
|
||
# 匹配模式:-[:RELATIONSHIP]-> 或 <-[:RELATIONSHIP]-
|
||
# 替换为:-[:RELATIONSHIP]-
|
||
|
||
# 替换 -> 为 -(在关系类型之后)
|
||
# 需要小心处理,只替换关系箭头,不替换其他地方的 ->
|
||
# 使用正则表达式匹配关系模式
|
||
fixed = cypher
|
||
|
||
# 替换 -[:RELATIONSHIP]-> 为 -[:RELATIONSHIP]-
|
||
fixed = re.sub(r'(\[:[^\]]+\])->', r'\1-', fixed)
|
||
|
||
# 替换 <-[:RELATIONSHIP]- 为 -[:RELATIONSHIP]-
|
||
fixed = re.sub(r'<-(\[:[^\]]+\])-', r'-\1-', fixed)
|
||
|
||
# 替换 -> 在关系模式中(处理可变长度关系,如 [*1..3]->)
|
||
fixed = re.sub(r'(\[[^\]]+\])->', r'\1-', fixed)
|
||
|
||
# 替换 <- 在关系模式中(处理可变长度关系,如 <-[*1..3]-)
|
||
fixed = re.sub(r'<-(\[[^\]]+\])-', r'-\1-', fixed)
|
||
|
||
# 如果进行了替换,记录日志
|
||
if fixed != cypher:
|
||
logger.info(f"[关系方向转换] 检测到有方向关系,已自动转换为无方向关系")
|
||
logger.debug(f"[关系方向转换] 原始: {cypher[:200]}...")
|
||
logger.debug(f"[关系方向转换] 转换后: {fixed[:200]}...")
|
||
|
||
return fixed
|
||
|
||
|
||
def fix_cypher_based_on_error(cypher: str, error_info: dict) -> str:
|
||
"""
|
||
基于实际错误信息智能修复Cypher
|
||
"""
|
||
if not error_info or not error_info.get("error_type"):
|
||
return None
|
||
|
||
error_type = error_info["error_type"]
|
||
error_message = error_info.get("error_message", "")
|
||
fixed = cypher
|
||
|
||
# ========== 根据错误类型进行修复 ==========
|
||
|
||
# 1. 语法错误:多余的括号
|
||
if "unexpected" in error_message.lower() and ("(" in error_message or ")" in error_message):
|
||
# 尝试修复多余的括号
|
||
# 匹配 `-[:REL]- ( (n:Label)` 这种模式
|
||
fixed = re.sub(r'(\[:[^\]]+\])-\s+\(\s*\(', r'\1-(', fixed)
|
||
fixed = re.sub(r'\)\s*-\s*(\[:[^\]]+\])-\s+\(\s*\(', r')-\1-(', fixed)
|
||
if fixed != cypher:
|
||
logger.info(f"[智能修复] 修复多余括号错误")
|
||
return fixed
|
||
|
||
# 2. 语法错误:缺少括号
|
||
if "missing" in error_message.lower() and ("(" in error_message or ")" in error_message):
|
||
# 检查括号是否匹配
|
||
open_count = fixed.count('(')
|
||
close_count = fixed.count(')')
|
||
if open_count > close_count:
|
||
# 缺少右括号,在末尾添加
|
||
fixed = fixed + ')' * (open_count - close_count)
|
||
logger.info(f"[智能修复] 添加缺失的右括号: {open_count - close_count} 个")
|
||
return fixed
|
||
elif close_count > open_count:
|
||
# 多余的右括号,但这种情况较难自动修复,返回None
|
||
logger.warning(f"[智能修复] 检测到多余的右括号,无法自动修复")
|
||
return None
|
||
|
||
# 3. 语法错误:关系定义问题
|
||
if "relationship" in error_message.lower() or "relationship type" in error_message.lower():
|
||
# 检查关系定义格式
|
||
# 修复 `-[:REL]- (n:Label)` -> `-[:REL]-(n:Label)`
|
||
fixed = re.sub(r'(\[:[^\]]+\])-\s+\(', r'\1-(', fixed)
|
||
if fixed != cypher:
|
||
logger.info(f"[智能修复] 修复关系定义格式")
|
||
return fixed
|
||
|
||
# 4. Schema错误:节点标签或关系类型不存在
|
||
if error_info["error_type"] == "schema_error":
|
||
keywords = error_info.get("keywords", [])
|
||
# 这里可以尝试其他修复策略
|
||
logger.debug(f"[智能修复] Schema错误,关键词: {keywords}")
|
||
# Schema错误通常需要LLM修正,这里不自动修复
|
||
return None
|
||
|
||
# 5. 语法错误:位置相关的修复
|
||
if error_info.get("error_position"):
|
||
pos = error_info["error_position"]
|
||
line_num = pos.get("line", 1)
|
||
col_num = pos.get("column", 1)
|
||
|
||
lines = fixed.split('\n')
|
||
if line_num <= len(lines):
|
||
error_line = lines[line_num - 1]
|
||
# 检查错误位置附近的字符
|
||
if col_num <= len(error_line):
|
||
char_at_error = error_line[col_num - 1] if col_num > 0 else ''
|
||
context = error_line[max(0, col_num-10):min(len(error_line), col_num+10)]
|
||
|
||
# 如果是多余的字符,尝试删除
|
||
if char_at_error in ['(', ')', ' ', '-']:
|
||
# 检查是否是明显的多余字符
|
||
before = error_line[:col_num-1] if col_num > 0 else ''
|
||
after = error_line[col_num:] if col_num < len(error_line) else ''
|
||
|
||
# 如果前后都是关系连接符,中间的字符可能是多余的
|
||
if re.search(r'[-\s]*$', before) and re.search(r'^[-\s]*', after):
|
||
# 尝试删除错误位置的字符
|
||
new_line = error_line[:col_num-1] + error_line[col_num:]
|
||
lines[line_num - 1] = new_line
|
||
fixed = '\n'.join(lines)
|
||
logger.info(f"[智能修复] 删除错误位置的字符: '{char_at_error}'")
|
||
return fixed
|
||
|
||
# 6. 通用修复:尝试修复常见的格式问题
|
||
# 修复关系后的多余空格
|
||
fixed = re.sub(r'(\[:[^\]]+\])-\s+\(', r'\1-(', fixed)
|
||
# 修复节点定义前的多余括号
|
||
fixed = re.sub(r'\(\s+\(([a-zA-Z_][a-zA-Z0-9_]*\s*:)', r'(\1', fixed)
|
||
|
||
if fixed != cypher:
|
||
logger.info(f"[智能修复] 应用通用修复")
|
||
return fixed
|
||
|
||
# 无法自动修复
|
||
return None
|
||
|
||
|
||
def fix_common_cypher_errors(cypher: str) -> str:
|
||
"""
|
||
修复常见的Cypher语法错误
|
||
"""
|
||
if not cypher:
|
||
return cypher
|
||
|
||
fixed = cypher
|
||
|
||
# ========== 修复1:关系后多余的空格和括号 ==========
|
||
# 匹配模式:`-[:REL]- ( (n:Label)` -> `-[:REL]-(n:Label)`
|
||
# 或者 `-[:REL]- (n:Label)` -> `-[:REL]-(n:Label)`
|
||
fixed = re.sub(r'(\[:[^\]]+\])-\s+\(\s*\(', r'\1-(', fixed) # `-[:REL]- ( (` -> `-[:REL]-(`
|
||
fixed = re.sub(r'(\[:[^\]]+\])-\s+\(([a-zA-Z_][a-zA-Z0-9_]*\s*:)', r'\1-(\2', fixed) # `-[:REL]- (n:` -> `-[:REL]-(n:`
|
||
|
||
# ========== 修复2:节点定义前多余的括号 ==========
|
||
# 匹配模式:`( (n:Label)` -> `(n:Label)`
|
||
fixed = re.sub(r'\(\s+\(([a-zA-Z_][a-zA-Z0-9_]*\s*:)', r'(\1', fixed)
|
||
|
||
# ========== 修复3:MATCH语句中的多余空格和括号 ==========
|
||
# 匹配模式:`MATCH ...)-[:REL]- ( (n:Label)` -> `MATCH ...)-[:REL]-(n:Label)`
|
||
fixed = re.sub(r'\)\s*-\s*(\[:[^\]]+\])-\s+\(\s*\(', r')-\1-(', fixed)
|
||
fixed = re.sub(r'\)\s*-\s*(\[:[^\]]+\])-\s+\(([a-zA-Z_][a-zA-Z0-9_]*\s*:)', r')-\1-(\2', fixed)
|
||
|
||
# ========== 修复4:规范化关系定义后的空格 ==========
|
||
# 确保关系定义后直接跟节点定义,如 `-[:REL]-(n:Label)` 而不是 `-[:REL]- (n:Label)`
|
||
fixed = re.sub(r'(\[:[^\]]+\])-\s+\(', r'\1-(', fixed)
|
||
|
||
if fixed != cypher:
|
||
logger.info(f"[Cypher修复] 检测到并修复了常见语法错误")
|
||
logger.debug(f"[Cypher修复] 原始: {cypher[:200]}...")
|
||
logger.debug(f"[Cypher修复] 修复后: {fixed[:200]}...")
|
||
|
||
return fixed
|