@@ -13,6 +13,7 @@ import (
13
13
"io/fs"
14
14
"iter"
15
15
"os"
16
+ "runtime"
16
17
"slices"
17
18
"strings"
18
19
"sync"
@@ -1033,3 +1034,79 @@ func unmarshalJSONString(data string, v any) error {
1033
1034
}
1034
1035
return nil
1035
1036
}
1037
+
1038
+ // readonlySavepoint starts a new SAVEPOINT.
1039
+ // The caller is responsible for calling endFn
1040
+ // to roll back the SAVEPOINT and remove it from the transaction stack.
1041
+ func readonlySavepoint (conn * sqlite.Conn ) (rollbackFunc func (), err error ) {
1042
+ name := "readonlySavepoint" // safe as names can be reused
1043
+ var pc [3 ]uintptr
1044
+ if n := runtime .Callers (0 , pc [:]); n > 0 {
1045
+ frames := runtime .CallersFrames (pc [:n ])
1046
+ if _ , more := frames .Next (); more { // runtime.Callers
1047
+ if _ , more := frames .Next (); more { // readonlySavepoint
1048
+ frame , _ := frames .Next () // caller we care about
1049
+ if frame .Function != "" {
1050
+ name = frame .Function
1051
+ }
1052
+ }
1053
+ }
1054
+ }
1055
+
1056
+ startedTransaction := conn .AutocommitEnabled ()
1057
+ if err := sqlitex .Execute (conn , `SAVEPOINT "` + name + `";` , nil ); err != nil {
1058
+ return nil , err
1059
+ }
1060
+ if startedTransaction {
1061
+ rollbackFunc = func () {
1062
+ panicError := recover ()
1063
+
1064
+ if conn .AutocommitEnabled () {
1065
+ // Transaction exited by application. Nothing to roll back.
1066
+ if panicError != nil {
1067
+ panic (panicError )
1068
+ }
1069
+ return
1070
+ }
1071
+
1072
+ // Always run ROLLBACK even if the connection has been interrupted.
1073
+ oldDoneChan := conn .SetInterrupt (nil )
1074
+ defer conn .SetInterrupt (oldDoneChan )
1075
+
1076
+ if err := sqlitex .Execute (conn , "ROLLBACK;" , nil ); err != nil {
1077
+ panic (err .Error ())
1078
+ }
1079
+ if panicError != nil {
1080
+ panic (panicError )
1081
+ }
1082
+ }
1083
+ } else {
1084
+ rollbackFunc = func () {
1085
+ panicError := recover ()
1086
+
1087
+ if conn .AutocommitEnabled () {
1088
+ // Transaction exited by application. Nothing to roll back.
1089
+ if panicError != nil {
1090
+ panic (panicError )
1091
+ }
1092
+ return
1093
+ }
1094
+
1095
+ // Always run ROLLBACK even if the connection has been interrupted.
1096
+ oldDoneChan := conn .SetInterrupt (nil )
1097
+ defer conn .SetInterrupt (oldDoneChan )
1098
+
1099
+ if err := sqlitex .Execute (conn , `ROLLBACK TO "` + name + `";` , nil ); err != nil {
1100
+ panic (err .Error ())
1101
+ }
1102
+ if err := sqlitex .Execute (conn , `RELEASE "` + name + `";` , nil ); err != nil {
1103
+ panic (err .Error ())
1104
+ }
1105
+ if panicError != nil {
1106
+ panic (panicError )
1107
+ }
1108
+ }
1109
+ }
1110
+
1111
+ return rollbackFunc , nil
1112
+ }
0 commit comments