mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 11:31:57 +08:00
🐛 fix(query-editor): 修复Oracle匿名块事务托管缺失
- 区分 BEGIN 事务控制语句与 Oracle 匿名块起始 - 让包含 DML 的 Oracle like 匿名块进入 SQL 编辑器托管事务 - 补充匿名块进入事务与回滚收口的后端回归测试
This commit is contained in:
@@ -1033,6 +1033,63 @@ func TestDBQueryMultiTransactionalUsesImplicitSessionTransactionForOracle(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiTransactionalKeepsOracleAnonymousBlockTransactionOpenUntilRollback(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
newDatabaseFunc = originalNewDatabaseFunc
|
||||
})
|
||||
|
||||
stmt := `BEGIN
|
||||
UPDATE users SET name = 'new' WHERE id = 1;
|
||||
DELETE FROM audit_logs WHERE user_id = 1;
|
||||
END;`
|
||||
fakeDB := &fakeBatchWriteDB{
|
||||
execAffected: map[string]int64{
|
||||
stmt: 2,
|
||||
},
|
||||
}
|
||||
newDatabaseFunc = func(dbType string) (db.Database, error) {
|
||||
return fakeDB, nil
|
||||
}
|
||||
|
||||
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
||||
config := connection.ConnectionConfig{Type: "oracle", Host: "127.0.0.1", Port: 1521, User: "app"}
|
||||
|
||||
result := app.DBQueryMultiTransactional(config, "ORCLPDB1", stmt, "oracle-anonymous-block-tx-query")
|
||||
if !result.Success {
|
||||
t.Fatalf("expected Oracle anonymous block transactional query success, got failure: %s", result.Message)
|
||||
}
|
||||
if result.TransactionID == "" || !result.TransactionPending {
|
||||
t.Fatalf("expected pending transaction metadata, got id=%q pending=%v", result.TransactionID, result.TransactionPending)
|
||||
}
|
||||
if fakeDB.session == nil {
|
||||
t.Fatal("expected Oracle anonymous block to open a pinned session")
|
||||
}
|
||||
if fakeDB.session.closed {
|
||||
t.Fatal("expected Oracle anonymous block transaction session to stay open before rollback")
|
||||
}
|
||||
if len(fakeDB.execQueries) != 1 || fakeDB.execQueries[0] != stmt {
|
||||
t.Fatalf("expected Oracle anonymous block to execute as a single statement before rollback, got %#v", fakeDB.execQueries)
|
||||
}
|
||||
|
||||
rollbackResult := app.DBRollbackTransaction(result.TransactionID)
|
||||
if !rollbackResult.Success {
|
||||
t.Fatalf("expected Oracle anonymous block rollback success, got failure: %s", rollbackResult.Message)
|
||||
}
|
||||
if !fakeDB.session.closed {
|
||||
t.Fatal("expected Oracle anonymous block transaction session to close after rollback")
|
||||
}
|
||||
wantExecs := []string{stmt, "ROLLBACK"}
|
||||
if len(fakeDB.execQueries) != len(wantExecs) {
|
||||
t.Fatalf("expected Oracle anonymous block rollback on pinned session, got %#v", fakeDB.execQueries)
|
||||
}
|
||||
for i, want := range wantExecs {
|
||||
if fakeDB.execQueries[i] != want {
|
||||
t.Fatalf("expected exec query %d = %q, got %q", i, want, fakeDB.execQueries[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiTransactionalOraclePrefersTransactionProviderForFinish(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
|
||||
@@ -394,6 +394,10 @@ func shouldUseManagedSQLTransaction(dbType string, query string) bool {
|
||||
if isReadOnlySQLQuery(dbType, stmt) {
|
||||
continue
|
||||
}
|
||||
if isOracleLikeAnonymousBlockManagedWrite(dbType, stmt) {
|
||||
hasManagedWrite = true
|
||||
continue
|
||||
}
|
||||
if isBatchableWriteSQLStatement(dbType, stmt) {
|
||||
hasManagedWrite = true
|
||||
continue
|
||||
@@ -425,9 +429,13 @@ func sqlEditorImplicitTransactionSQL(dbType string) (commitSQL string, rollbackS
|
||||
}
|
||||
|
||||
func isSQLTransactionControlStatement(stmt string) bool {
|
||||
switch leadingSQLKeyword(stmt) {
|
||||
keyword, keywordEnd := nextSQLKeyword(stmt, 0)
|
||||
switch keyword {
|
||||
case "begin", "commit", "rollback", "savepoint", "release":
|
||||
return true
|
||||
if keyword != "begin" {
|
||||
return true
|
||||
}
|
||||
return isBeginTransactionControlStatement(stmt, keywordEnd)
|
||||
case "start":
|
||||
return strings.Contains(strings.ToLower(stmt), "transaction")
|
||||
default:
|
||||
@@ -435,6 +443,38 @@ func isSQLTransactionControlStatement(stmt string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func isBeginTransactionControlStatement(stmt string, keywordEnd int) bool {
|
||||
switch nextSQLSignificantByte(stmt, keywordEnd) {
|
||||
case 0, ';':
|
||||
return true
|
||||
}
|
||||
|
||||
switch nextSQLSignificantToken(stmt, keywordEnd) {
|
||||
case "transaction", "tran", "work", "isolation", "read", "write", "deferred", "immediate", "exclusive", "distributed":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isOracleLikeAnonymousBlockManagedWrite(dbType string, stmt string) bool {
|
||||
if !isOracleLikeDBType(dbType) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch nextSQLSignificantToken(strings.TrimSpace(stmt), 0) {
|
||||
case "begin", "declare":
|
||||
return sqlContainsKeyword(stmt, "insert") ||
|
||||
sqlContainsKeyword(stmt, "update") ||
|
||||
sqlContainsKeyword(stmt, "delete") ||
|
||||
sqlContainsKeyword(stmt, "merge") ||
|
||||
sqlContainsKeyword(stmt, "replace") ||
|
||||
sqlContainsKeyword(stmt, "upsert")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) DBCommitTransaction(transactionID string) connection.QueryResult {
|
||||
return a.finishManagedSQLTransaction(transactionID, true)
|
||||
}
|
||||
|
||||
@@ -27,3 +27,26 @@ func TestShouldUseManagedSQLTransaction_UnsupportedTypesUsePlainExecution(t *tes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldUseManagedSQLTransaction_OracleAnonymousBlockWithDMLUsesManagedTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
query := `BEGIN
|
||||
UPDATE users SET name = 'new' WHERE id = 1;
|
||||
DELETE FROM audit_logs WHERE user_id = 1;
|
||||
END;`
|
||||
if !shouldUseManagedSQLTransaction("oracle", query) {
|
||||
t.Fatal("expected Oracle anonymous block with DML to use SQL editor managed transaction")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldUseManagedSQLTransaction_OracleReadOnlyAnonymousBlockStaysUnmanaged(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
query := `BEGIN
|
||||
NULL;
|
||||
END;`
|
||||
if shouldUseManagedSQLTransaction("oracle", query) {
|
||||
t.Fatal("expected Oracle read-only anonymous block to stay unmanaged")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user