1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 package stdlib
67
68 import (
69 "context"
70 "database/sql"
71 "database/sql/driver"
72 "errors"
73 "fmt"
74 "io"
75 "math"
76 "math/rand"
77 "reflect"
78 "strconv"
79 "strings"
80 "sync"
81 "time"
82
83 "github.com/jackc/pgx/v5"
84 "github.com/jackc/pgx/v5/pgconn"
85 "github.com/jackc/pgx/v5/pgtype"
86 "github.com/jackc/pgx/v5/pgxpool"
87 )
88
89
90 var databaseSQLResultFormats pgx.QueryResultFormatsByOID
91
92 var pgxDriver *Driver
93
94 func init() {
95 pgxDriver = &Driver{
96 configs: make(map[string]*pgx.ConnConfig),
97 }
98
99
100
101 if !contains(sql.Drivers(), "pgx") {
102 sql.Register("pgx", pgxDriver)
103 }
104 sql.Register("pgx/v5", pgxDriver)
105
106 databaseSQLResultFormats = pgx.QueryResultFormatsByOID{
107 pgtype.BoolOID: 1,
108 pgtype.ByteaOID: 1,
109 pgtype.CIDOID: 1,
110 pgtype.DateOID: 1,
111 pgtype.Float4OID: 1,
112 pgtype.Float8OID: 1,
113 pgtype.Int2OID: 1,
114 pgtype.Int4OID: 1,
115 pgtype.Int8OID: 1,
116 pgtype.OIDOID: 1,
117 pgtype.TimestampOID: 1,
118 pgtype.TimestamptzOID: 1,
119 pgtype.XIDOID: 1,
120 }
121 }
122
123
124
125 func contains(list []string, y string) bool {
126 for _, x := range list {
127 if x == y {
128 return true
129 }
130 }
131 return false
132 }
133
134
135 type OptionOpenDB func(*connector)
136
137
138
139 func OptionBeforeConnect(bc func(context.Context, *pgx.ConnConfig) error) OptionOpenDB {
140 return func(dc *connector) {
141 dc.BeforeConnect = bc
142 }
143 }
144
145
146 func OptionAfterConnect(ac func(context.Context, *pgx.Conn) error) OptionOpenDB {
147 return func(dc *connector) {
148 dc.AfterConnect = ac
149 }
150 }
151
152
153
154
155 func OptionResetSession(rs func(context.Context, *pgx.Conn) error) OptionOpenDB {
156 return func(dc *connector) {
157 dc.ResetSession = rs
158 }
159 }
160
161
162
163
164
165 func RandomizeHostOrderFunc(ctx context.Context, connConfig *pgx.ConnConfig) error {
166 if len(connConfig.Fallbacks) == 0 {
167 return nil
168 }
169
170 newFallbacks := append([]*pgconn.FallbackConfig{{
171 Host: connConfig.Host,
172 Port: connConfig.Port,
173 TLSConfig: connConfig.TLSConfig,
174 }}, connConfig.Fallbacks...)
175
176 rand.Shuffle(len(newFallbacks), func(i, j int) {
177 newFallbacks[i], newFallbacks[j] = newFallbacks[j], newFallbacks[i]
178 })
179
180
181 newPrimary := newFallbacks[len(newFallbacks)-1]
182 connConfig.Host = newPrimary.Host
183 connConfig.Port = newPrimary.Port
184 connConfig.TLSConfig = newPrimary.TLSConfig
185 connConfig.Fallbacks = newFallbacks[:len(newFallbacks)-1]
186 return nil
187 }
188
189 func GetConnector(config pgx.ConnConfig, opts ...OptionOpenDB) driver.Connector {
190 c := connector{
191 ConnConfig: config,
192 BeforeConnect: func(context.Context, *pgx.ConnConfig) error { return nil },
193 AfterConnect: func(context.Context, *pgx.Conn) error { return nil },
194 ResetSession: func(context.Context, *pgx.Conn) error { return nil },
195 driver: pgxDriver,
196 }
197
198 for _, opt := range opts {
199 opt(&c)
200 }
201 return c
202 }
203
204
205
206
207
208 func GetPoolConnector(pool *pgxpool.Pool, opts ...OptionOpenDB) driver.Connector {
209 c := connector{
210 pool: pool,
211 ResetSession: func(context.Context, *pgx.Conn) error { return nil },
212 driver: pgxDriver,
213 }
214
215 for _, opt := range opts {
216 opt(&c)
217 }
218
219 return c
220 }
221
222 func OpenDB(config pgx.ConnConfig, opts ...OptionOpenDB) *sql.DB {
223 c := GetConnector(config, opts...)
224 return sql.OpenDB(c)
225 }
226
227
228
229
230 func OpenDBFromPool(pool *pgxpool.Pool, opts ...OptionOpenDB) *sql.DB {
231 c := GetPoolConnector(pool, opts...)
232 db := sql.OpenDB(c)
233 db.SetMaxIdleConns(0)
234 return db
235 }
236
237 type connector struct {
238 pgx.ConnConfig
239 pool *pgxpool.Pool
240 BeforeConnect func(context.Context, *pgx.ConnConfig) error
241 AfterConnect func(context.Context, *pgx.Conn) error
242 ResetSession func(context.Context, *pgx.Conn) error
243 driver *Driver
244 }
245
246
247 func (c connector) Connect(ctx context.Context) (driver.Conn, error) {
248 var (
249 connConfig pgx.ConnConfig
250 conn *pgx.Conn
251 close func(context.Context) error
252 err error
253 )
254
255 if c.pool == nil {
256
257 connConfig = c.ConnConfig
258
259 if err = c.BeforeConnect(ctx, &connConfig); err != nil {
260 return nil, err
261 }
262
263 if conn, err = pgx.ConnectConfig(ctx, &connConfig); err != nil {
264 return nil, err
265 }
266
267 if err = c.AfterConnect(ctx, conn); err != nil {
268 return nil, err
269 }
270
271 close = conn.Close
272 } else {
273 var pconn *pgxpool.Conn
274
275 pconn, err = c.pool.Acquire(ctx)
276 if err != nil {
277 return nil, err
278 }
279
280 conn = pconn.Conn()
281
282 close = func(_ context.Context) error {
283 pconn.Release()
284 return nil
285 }
286 }
287
288 return &Conn{
289 conn: conn,
290 close: close,
291 driver: c.driver,
292 connConfig: connConfig,
293 resetSessionFunc: c.ResetSession,
294 psRefCounts: make(map[*pgconn.StatementDescription]int),
295 }, nil
296 }
297
298
299 func (c connector) Driver() driver.Driver {
300 return c.driver
301 }
302
303
304
305 func GetDefaultDriver() driver.Driver {
306 return pgxDriver
307 }
308
309 type Driver struct {
310 configMutex sync.Mutex
311 configs map[string]*pgx.ConnConfig
312 sequence int
313 }
314
315 func (d *Driver) Open(name string) (driver.Conn, error) {
316 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
317 defer cancel()
318
319 connector, err := d.OpenConnector(name)
320 if err != nil {
321 return nil, err
322 }
323 return connector.Connect(ctx)
324 }
325
326 func (d *Driver) OpenConnector(name string) (driver.Connector, error) {
327 return &driverConnector{driver: d, name: name}, nil
328 }
329
330 func (d *Driver) registerConnConfig(c *pgx.ConnConfig) string {
331 d.configMutex.Lock()
332 connStr := fmt.Sprintf("registeredConnConfig%d", d.sequence)
333 d.sequence++
334 d.configs[connStr] = c
335 d.configMutex.Unlock()
336 return connStr
337 }
338
339 func (d *Driver) unregisterConnConfig(connStr string) {
340 d.configMutex.Lock()
341 delete(d.configs, connStr)
342 d.configMutex.Unlock()
343 }
344
345 type driverConnector struct {
346 driver *Driver
347 name string
348 }
349
350 func (dc *driverConnector) Connect(ctx context.Context) (driver.Conn, error) {
351 var connConfig *pgx.ConnConfig
352
353 dc.driver.configMutex.Lock()
354 connConfig = dc.driver.configs[dc.name]
355 dc.driver.configMutex.Unlock()
356
357 if connConfig == nil {
358 var err error
359 connConfig, err = pgx.ParseConfig(dc.name)
360 if err != nil {
361 return nil, err
362 }
363 }
364
365 conn, err := pgx.ConnectConfig(ctx, connConfig)
366 if err != nil {
367 return nil, err
368 }
369
370 c := &Conn{
371 conn: conn,
372 close: conn.Close,
373 driver: dc.driver,
374 connConfig: *connConfig,
375 resetSessionFunc: func(context.Context, *pgx.Conn) error { return nil },
376 psRefCounts: make(map[*pgconn.StatementDescription]int),
377 }
378
379 return c, nil
380 }
381
382 func (dc *driverConnector) Driver() driver.Driver {
383 return dc.driver
384 }
385
386
387 func RegisterConnConfig(c *pgx.ConnConfig) string {
388 return pgxDriver.registerConnConfig(c)
389 }
390
391
392 func UnregisterConnConfig(connStr string) {
393 pgxDriver.unregisterConnConfig(connStr)
394 }
395
396 type Conn struct {
397 conn *pgx.Conn
398 close func(context.Context) error
399 driver *Driver
400 connConfig pgx.ConnConfig
401 resetSessionFunc func(context.Context, *pgx.Conn) error
402 lastResetSessionTime time.Time
403
404
405
406
407
408
409
410 psRefCounts map[*pgconn.StatementDescription]int
411 }
412
413
414 func (c *Conn) Conn() *pgx.Conn {
415 return c.conn
416 }
417
418 func (c *Conn) Prepare(query string) (driver.Stmt, error) {
419 return c.PrepareContext(context.Background(), query)
420 }
421
422 func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
423 if c.conn.IsClosed() {
424 return nil, driver.ErrBadConn
425 }
426
427 sd, err := c.conn.Prepare(ctx, query, query)
428 if err != nil {
429 return nil, err
430 }
431 c.psRefCounts[sd]++
432
433 return &Stmt{sd: sd, conn: c}, nil
434 }
435
436 func (c *Conn) Close() error {
437 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
438 defer cancel()
439 return c.close(ctx)
440 }
441
442 func (c *Conn) Begin() (driver.Tx, error) {
443 return c.BeginTx(context.Background(), driver.TxOptions{})
444 }
445
446 func (c *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
447 if c.conn.IsClosed() {
448 return nil, driver.ErrBadConn
449 }
450
451 var pgxOpts pgx.TxOptions
452 switch sql.IsolationLevel(opts.Isolation) {
453 case sql.LevelDefault:
454 case sql.LevelReadUncommitted:
455 pgxOpts.IsoLevel = pgx.ReadUncommitted
456 case sql.LevelReadCommitted:
457 pgxOpts.IsoLevel = pgx.ReadCommitted
458 case sql.LevelRepeatableRead, sql.LevelSnapshot:
459 pgxOpts.IsoLevel = pgx.RepeatableRead
460 case sql.LevelSerializable:
461 pgxOpts.IsoLevel = pgx.Serializable
462 default:
463 return nil, fmt.Errorf("unsupported isolation: %v", opts.Isolation)
464 }
465
466 if opts.ReadOnly {
467 pgxOpts.AccessMode = pgx.ReadOnly
468 }
469
470 tx, err := c.conn.BeginTx(ctx, pgxOpts)
471 if err != nil {
472 return nil, err
473 }
474
475 return wrapTx{ctx: ctx, tx: tx}, nil
476 }
477
478 func (c *Conn) ExecContext(ctx context.Context, query string, argsV []driver.NamedValue) (driver.Result, error) {
479 if c.conn.IsClosed() {
480 return nil, driver.ErrBadConn
481 }
482
483 args := namedValueToInterface(argsV)
484
485 commandTag, err := c.conn.Exec(ctx, query, args...)
486
487 if err != nil {
488 if pgconn.SafeToRetry(err) {
489 return nil, driver.ErrBadConn
490 }
491 }
492 return driver.RowsAffected(commandTag.RowsAffected()), err
493 }
494
495 func (c *Conn) QueryContext(ctx context.Context, query string, argsV []driver.NamedValue) (driver.Rows, error) {
496 if c.conn.IsClosed() {
497 return nil, driver.ErrBadConn
498 }
499
500 args := []any{databaseSQLResultFormats}
501 args = append(args, namedValueToInterface(argsV)...)
502
503 rows, err := c.conn.Query(ctx, query, args...)
504 if err != nil {
505 if pgconn.SafeToRetry(err) {
506 return nil, driver.ErrBadConn
507 }
508 return nil, err
509 }
510
511
512 more := rows.Next()
513 if err = rows.Err(); err != nil {
514 rows.Close()
515 return nil, err
516 }
517 return &Rows{conn: c, rows: rows, skipNext: true, skipNextMore: more}, nil
518 }
519
520 func (c *Conn) Ping(ctx context.Context) error {
521 if c.conn.IsClosed() {
522 return driver.ErrBadConn
523 }
524
525 err := c.conn.Ping(ctx)
526 if err != nil {
527
528
529 c.Close()
530 return driver.ErrBadConn
531 }
532
533 return nil
534 }
535
536 func (c *Conn) CheckNamedValue(*driver.NamedValue) error {
537
538 return nil
539 }
540
541 func (c *Conn) ResetSession(ctx context.Context) error {
542 if c.conn.IsClosed() {
543 return driver.ErrBadConn
544 }
545
546 now := time.Now()
547 if now.Sub(c.lastResetSessionTime) > time.Second {
548 if err := c.conn.PgConn().Ping(ctx); err != nil {
549 return driver.ErrBadConn
550 }
551 }
552 c.lastResetSessionTime = now
553
554 return c.resetSessionFunc(ctx, c.conn)
555 }
556
557 type Stmt struct {
558 sd *pgconn.StatementDescription
559 conn *Conn
560 }
561
562 func (s *Stmt) Close() error {
563 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
564 defer cancel()
565
566 refCount := s.conn.psRefCounts[s.sd]
567 if refCount == 1 {
568 delete(s.conn.psRefCounts, s.sd)
569 } else {
570 s.conn.psRefCounts[s.sd]--
571 return nil
572 }
573
574 return s.conn.conn.Deallocate(ctx, s.sd.SQL)
575 }
576
577 func (s *Stmt) NumInput() int {
578 return len(s.sd.ParamOIDs)
579 }
580
581 func (s *Stmt) Exec(argsV []driver.Value) (driver.Result, error) {
582 return nil, errors.New("Stmt.Exec deprecated and not implemented")
583 }
584
585 func (s *Stmt) ExecContext(ctx context.Context, argsV []driver.NamedValue) (driver.Result, error) {
586 return s.conn.ExecContext(ctx, s.sd.SQL, argsV)
587 }
588
589 func (s *Stmt) Query(argsV []driver.Value) (driver.Rows, error) {
590 return nil, errors.New("Stmt.Query deprecated and not implemented")
591 }
592
593 func (s *Stmt) QueryContext(ctx context.Context, argsV []driver.NamedValue) (driver.Rows, error) {
594 return s.conn.QueryContext(ctx, s.sd.SQL, argsV)
595 }
596
597 type rowValueFunc func(src []byte) (driver.Value, error)
598
599 type Rows struct {
600 conn *Conn
601 rows pgx.Rows
602 valueFuncs []rowValueFunc
603 skipNext bool
604 skipNextMore bool
605
606 columnNames []string
607 }
608
609 func (r *Rows) Columns() []string {
610 if r.columnNames == nil {
611 fields := r.rows.FieldDescriptions()
612 r.columnNames = make([]string, len(fields))
613 for i, fd := range fields {
614 r.columnNames[i] = string(fd.Name)
615 }
616 }
617
618 return r.columnNames
619 }
620
621
622 func (r *Rows) ColumnTypeDatabaseTypeName(index int) string {
623 if dt, ok := r.conn.conn.TypeMap().TypeForOID(r.rows.FieldDescriptions()[index].DataTypeOID); ok {
624 return strings.ToUpper(dt.Name)
625 }
626
627 return strconv.FormatInt(int64(r.rows.FieldDescriptions()[index].DataTypeOID), 10)
628 }
629
630 const varHeaderSize = 4
631
632
633
634
635 func (r *Rows) ColumnTypeLength(index int) (int64, bool) {
636 fd := r.rows.FieldDescriptions()[index]
637
638 switch fd.DataTypeOID {
639 case pgtype.TextOID, pgtype.ByteaOID:
640 return math.MaxInt64, true
641 case pgtype.VarcharOID, pgtype.BPCharArrayOID:
642 return int64(fd.TypeModifier - varHeaderSize), true
643 default:
644 return 0, false
645 }
646 }
647
648
649
650 func (r *Rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
651 fd := r.rows.FieldDescriptions()[index]
652
653 switch fd.DataTypeOID {
654 case pgtype.NumericOID:
655 mod := fd.TypeModifier - varHeaderSize
656 precision = int64((mod >> 16) & 0xffff)
657 scale = int64(mod & 0xffff)
658 return precision, scale, true
659 default:
660 return 0, 0, false
661 }
662 }
663
664
665 func (r *Rows) ColumnTypeScanType(index int) reflect.Type {
666 fd := r.rows.FieldDescriptions()[index]
667
668 switch fd.DataTypeOID {
669 case pgtype.Float8OID:
670 return reflect.TypeOf(float64(0))
671 case pgtype.Float4OID:
672 return reflect.TypeOf(float32(0))
673 case pgtype.Int8OID:
674 return reflect.TypeOf(int64(0))
675 case pgtype.Int4OID:
676 return reflect.TypeOf(int32(0))
677 case pgtype.Int2OID:
678 return reflect.TypeOf(int16(0))
679 case pgtype.BoolOID:
680 return reflect.TypeOf(false)
681 case pgtype.NumericOID:
682 return reflect.TypeOf(float64(0))
683 case pgtype.DateOID, pgtype.TimestampOID, pgtype.TimestamptzOID:
684 return reflect.TypeOf(time.Time{})
685 case pgtype.ByteaOID:
686 return reflect.TypeOf([]byte(nil))
687 default:
688 return reflect.TypeOf("")
689 }
690 }
691
692 func (r *Rows) Close() error {
693 r.rows.Close()
694 return r.rows.Err()
695 }
696
697 func (r *Rows) Next(dest []driver.Value) error {
698 m := r.conn.conn.TypeMap()
699 fieldDescriptions := r.rows.FieldDescriptions()
700
701 if r.valueFuncs == nil {
702 r.valueFuncs = make([]rowValueFunc, len(fieldDescriptions))
703
704 for i, fd := range fieldDescriptions {
705 dataTypeOID := fd.DataTypeOID
706 format := fd.Format
707
708 switch fd.DataTypeOID {
709 case pgtype.BoolOID:
710 var d bool
711 scanPlan := m.PlanScan(dataTypeOID, format, &d)
712 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
713 err := scanPlan.Scan(src, &d)
714 return d, err
715 }
716 case pgtype.ByteaOID:
717 var d []byte
718 scanPlan := m.PlanScan(dataTypeOID, format, &d)
719 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
720 err := scanPlan.Scan(src, &d)
721 return d, err
722 }
723 case pgtype.CIDOID, pgtype.OIDOID, pgtype.XIDOID:
724 var d pgtype.Uint32
725 scanPlan := m.PlanScan(dataTypeOID, format, &d)
726 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
727 err := scanPlan.Scan(src, &d)
728 if err != nil {
729 return nil, err
730 }
731 return d.Value()
732 }
733 case pgtype.DateOID:
734 var d pgtype.Date
735 scanPlan := m.PlanScan(dataTypeOID, format, &d)
736 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
737 err := scanPlan.Scan(src, &d)
738 if err != nil {
739 return nil, err
740 }
741 return d.Value()
742 }
743 case pgtype.Float4OID:
744 var d float32
745 scanPlan := m.PlanScan(dataTypeOID, format, &d)
746 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
747 err := scanPlan.Scan(src, &d)
748 return float64(d), err
749 }
750 case pgtype.Float8OID:
751 var d float64
752 scanPlan := m.PlanScan(dataTypeOID, format, &d)
753 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
754 err := scanPlan.Scan(src, &d)
755 return d, err
756 }
757 case pgtype.Int2OID:
758 var d int16
759 scanPlan := m.PlanScan(dataTypeOID, format, &d)
760 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
761 err := scanPlan.Scan(src, &d)
762 return int64(d), err
763 }
764 case pgtype.Int4OID:
765 var d int32
766 scanPlan := m.PlanScan(dataTypeOID, format, &d)
767 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
768 err := scanPlan.Scan(src, &d)
769 return int64(d), err
770 }
771 case pgtype.Int8OID:
772 var d int64
773 scanPlan := m.PlanScan(dataTypeOID, format, &d)
774 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
775 err := scanPlan.Scan(src, &d)
776 return d, err
777 }
778 case pgtype.JSONOID, pgtype.JSONBOID:
779 var d []byte
780 scanPlan := m.PlanScan(dataTypeOID, format, &d)
781 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
782 err := scanPlan.Scan(src, &d)
783 if err != nil {
784 return nil, err
785 }
786 return d, nil
787 }
788 case pgtype.TimestampOID:
789 var d pgtype.Timestamp
790 scanPlan := m.PlanScan(dataTypeOID, format, &d)
791 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
792 err := scanPlan.Scan(src, &d)
793 if err != nil {
794 return nil, err
795 }
796 return d.Value()
797 }
798 case pgtype.TimestamptzOID:
799 var d pgtype.Timestamptz
800 scanPlan := m.PlanScan(dataTypeOID, format, &d)
801 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
802 err := scanPlan.Scan(src, &d)
803 if err != nil {
804 return nil, err
805 }
806 return d.Value()
807 }
808 default:
809 var d string
810 scanPlan := m.PlanScan(dataTypeOID, format, &d)
811 r.valueFuncs[i] = func(src []byte) (driver.Value, error) {
812 err := scanPlan.Scan(src, &d)
813 return d, err
814 }
815 }
816 }
817 }
818
819 var more bool
820 if r.skipNext {
821 more = r.skipNextMore
822 r.skipNext = false
823 } else {
824 more = r.rows.Next()
825 }
826
827 if !more {
828 if r.rows.Err() == nil {
829 return io.EOF
830 } else {
831 return r.rows.Err()
832 }
833 }
834
835 for i, rv := range r.rows.RawValues() {
836 if rv != nil {
837 var err error
838 dest[i], err = r.valueFuncs[i](rv)
839 if err != nil {
840 return fmt.Errorf("convert field %d failed: %w", i, err)
841 }
842 } else {
843 dest[i] = nil
844 }
845 }
846
847 return nil
848 }
849
850 func valueToInterface(argsV []driver.Value) []any {
851 args := make([]any, 0, len(argsV))
852 for _, v := range argsV {
853 if v != nil {
854 args = append(args, v.(any))
855 } else {
856 args = append(args, nil)
857 }
858 }
859 return args
860 }
861
862 func namedValueToInterface(argsV []driver.NamedValue) []any {
863 args := make([]any, 0, len(argsV))
864 for _, v := range argsV {
865 if v.Value != nil {
866 args = append(args, v.Value.(any))
867 } else {
868 args = append(args, nil)
869 }
870 }
871 return args
872 }
873
874 type wrapTx struct {
875 ctx context.Context
876 tx pgx.Tx
877 }
878
879 func (wtx wrapTx) Commit() error { return wtx.tx.Commit(wtx.ctx) }
880
881 func (wtx wrapTx) Rollback() error { return wtx.tx.Rollback(wtx.ctx) }
882
View as plain text