🐛 fix(sql-editor): 修复存储过程与返回结果写语句的结果识别

- 补齐 SQL 分类逻辑,识别 SQL Server 裸存储过程调用、RETURNING/OUTPUT、SELECT INTO 及消息块场景
- 调整多语句执行与批量写入分支,避免返回行或服务端消息被 Exec 路径吞掉
- 为 PostgreSQL、OpenGauss、Kingbase、HighGo 补充 notice 回传能力并增加回归测试
This commit is contained in:
Syngnat
2026-06-14 21:37:02 +08:00
parent d7632e29a6
commit f2ffeeaf45
9 changed files with 668 additions and 15 deletions

View File

@@ -336,6 +336,105 @@ func isSQLKeywordByte(ch byte) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
}
func normalizeSQLClassifierDBType(dbType string) string {
normalized := strings.ToLower(strings.TrimSpace(dbType))
switch normalized {
case "postgresql":
return "postgres"
case "mssql", "sql_server", "sql-server":
return "sqlserver"
case "open_gauss", "open-gauss":
return "opengauss"
case "gauss_db", "gauss-db":
return "gaussdb"
case "kingbase8", "kingbasees", "kingbasev8":
return "kingbase"
default:
return normalized
}
}
func isSQLServerDBType(dbType string) bool {
return normalizeSQLClassifierDBType(dbType) == "sqlserver"
}
func isOracleLikeDBType(dbType string) bool {
switch normalizeSQLClassifierDBType(dbType) {
case "oracle", "dameng":
return true
default:
return false
}
}
func isPostgresNoticeCapableDBType(dbType string) bool {
switch normalizeSQLClassifierDBType(dbType) {
case "postgres", "opengauss":
return true
default:
return false
}
}
func isSQLSelectIntoStatement(query string) bool {
keyword, _ := sqlDataOperationInfo(query)
return keyword == "select" && findTopLevelSQLKeyword(query, 0, "into") >= 0
}
func sqlContainsKeyword(text string, want string) bool {
for pos := 0; pos < len(text); {
if next, ok := skipSQLQuotedOrComment(text, pos); ok {
pos = next
continue
}
if isSQLKeywordByte(text[pos]) {
end := pos + 1
for end < len(text) && isSQLKeywordByte(text[end]) {
end++
}
if strings.EqualFold(text[pos:end], want) {
return true
}
pos = end
continue
}
pos++
}
return false
}
func sqlWriteStatementReturnsRows(dbType string, query string) bool {
keyword, withHasWrite := sqlDataOperationInfo(query)
if withHasWrite && keyword == "select" {
return true
}
if !isSQLDataWriteKeyword(keyword) {
return false
}
if !isOracleLikeDBType(dbType) && findTopLevelSQLKeyword(query, 0, "returning") >= 0 {
return true
}
if isSQLServerDBType(dbType) && findTopLevelSQLKeyword(query, 0, "output") >= 0 {
return true
}
return false
}
func sqlServerControlFlowMayReturnMessages(query string) bool {
switch leadingSQLKeyword(query) {
case "if", "begin", "while", "declare", "try", "catch":
return sqlContainsKeyword(query, "exec") ||
sqlContainsKeyword(query, "execute") ||
sqlContainsKeyword(query, "print") ||
sqlContainsKeyword(query, "raiserror") ||
sqlContainsKeyword(query, "throw") ||
sqlContainsKeyword(query, "dbcc") ||
sqlContainsKeyword(query, "set")
default:
return false
}
}
func isReadOnlySQLQuery(dbType string, query string) bool {
if strings.ToLower(strings.TrimSpace(dbType)) == "mongodb" && strings.HasPrefix(strings.TrimSpace(query), "{") {
return true
@@ -345,6 +444,9 @@ func isReadOnlySQLQuery(dbType string, query string) bool {
if withHasWrite {
return false
}
if keyword == "select" && isSQLSelectIntoStatement(query) {
return false
}
switch keyword {
case "select", "with", "show", "describe", "desc", "explain", "pragma", "values", "consume":
return true
@@ -362,6 +464,9 @@ func isBatchableWriteSQLStatement(dbType string, query string) bool {
if withHasWrite {
return true
}
if keyword == "select" && isSQLSelectIntoStatement(query) {
return true
}
return isSQLDataWriteKeyword(keyword)
}