Skip to content

Commit 03cac2d

Browse files
authored
Merge pull request #337 from ydb-platform/aborted
added test for Operation error ABORTED (Transaction locks invalidated)
2 parents 7dd9ec4 + e23f888 commit 03cac2d

File tree

4 files changed

+138
-38
lines changed

4 files changed

+138
-38
lines changed

internal/xsql/conn.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,12 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
153153
return nil, errClosedConn
154154
}
155155
m := queryModeFromContext(ctx, c.defaultQueryMode)
156-
if c.currentTx != nil && m == DataQueryMode {
156+
if c.currentTx != nil {
157+
if m != DataQueryMode {
158+
return nil, xerrors.WithStackTrace(
159+
fmt.Errorf("query mode `%s` not supported with transaction", m.String()),
160+
)
161+
}
157162
return c.currentTx.ExecContext(ctx, query, args)
158163
}
159164
switch m {
@@ -188,7 +193,12 @@ func (c *conn) QueryContext(ctx context.Context, query string, args []driver.Nam
188193
return nil, errClosedConn
189194
}
190195
m := queryModeFromContext(ctx, c.defaultQueryMode)
191-
if c.currentTx != nil && m == DataQueryMode {
196+
if c.currentTx != nil {
197+
if m != DataQueryMode {
198+
return nil, xerrors.WithStackTrace(
199+
fmt.Errorf("query mode `%s` not supported with transaction", m.String()),
200+
)
201+
}
192202
return c.currentTx.QueryContext(ctx, query, args)
193203
}
194204
switch m {

retry/sql.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66
"database/sql/driver"
7+
"fmt"
78

89
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
910
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/isolation"
@@ -51,19 +52,23 @@ func WithTxSettings(txControl *table.TransactionSettings) DoTxOption {
5152

5253
// DoTx is a shortcut for calling Do(ctx, f) on initialized TxDoer with DB field set to given db.
5354
func DoTx(ctx context.Context, db *sql.DB, f TxOperationFunc, opts ...DoTxOption) error {
54-
options := doTxOptions{
55-
txOptions: &sql.TxOptions{
56-
Isolation: sql.LevelDefault,
57-
ReadOnly: false,
58-
},
59-
idempotent: false,
60-
}
55+
var (
56+
options = doTxOptions{
57+
txOptions: &sql.TxOptions{
58+
Isolation: sql.LevelDefault,
59+
ReadOnly: false,
60+
},
61+
idempotent: false,
62+
}
63+
attempts = 0
64+
)
6165
for _, o := range opts {
6266
if err := o(&options); err != nil {
6367
return xerrors.WithStackTrace(err)
6468
}
6569
}
6670
err := Retry(ctx, func(ctx context.Context) (err error) {
71+
attempts++
6772
tx, err := db.BeginTx(ctx, options.txOptions)
6873
if err != nil {
6974
return xerrors.WithStackTrace(err)
@@ -85,7 +90,9 @@ func DoTx(ctx context.Context, db *sql.DB, f TxOperationFunc, opts ...DoTxOption
8590
return nil
8691
}, WithIdempotent(options.idempotent))
8792
if err != nil {
88-
return xerrors.WithStackTrace(err)
93+
return xerrors.WithStackTrace(
94+
fmt.Errorf("opration failed with %d attempts: %w", attempts, err),
95+
)
8996
}
9097
return nil
9198
}

retry/sql_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package retry
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"database/sql/driver"
7+
"testing"
8+
"time"
9+
10+
"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
11+
"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue"
12+
13+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
14+
)
15+
16+
type mock struct{}
17+
18+
func (c mock) Commit() error {
19+
return nil
20+
}
21+
22+
func (c mock) Rollback() error {
23+
return nil
24+
}
25+
26+
func (c mock) Open(name string) (driver.Conn, error) {
27+
return c, nil
28+
}
29+
30+
func (c mock) Prepare(query string) (driver.Stmt, error) {
31+
return nil, nil
32+
}
33+
34+
func (c mock) Close() error {
35+
return nil
36+
}
37+
38+
func (c mock) Begin() (driver.Tx, error) {
39+
return c, nil
40+
}
41+
42+
func (c mock) Connect(ctx context.Context) (driver.Conn, error) {
43+
return c, nil
44+
}
45+
46+
func (c mock) Driver() driver.Driver {
47+
return c
48+
}
49+
50+
func (c mock) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
51+
return c, nil
52+
}
53+
54+
func TestDoTxAbortedTLI(t *testing.T) {
55+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
56+
defer cancel()
57+
db := sql.OpenDB(mock{})
58+
var counter int
59+
if err := DoTx(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
60+
counter++
61+
if counter > 10 {
62+
return nil
63+
}
64+
return xerrors.Operation(
65+
xerrors.WithStatusCode(Ydb.StatusIds_ABORTED),
66+
xerrors.WithIssues([]*Ydb_Issue.IssueMessage{
67+
{IssueCode: 2001, Message: "Transaction locks invalidated"},
68+
}),
69+
)
70+
}); err != nil {
71+
t.Errorf("retry operation failed: %v", err)
72+
}
73+
if counter <= 1 {
74+
t.Errorf("nothing attempts: %d", counter)
75+
}
76+
}

sql_e2e_test.go

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,35 +66,42 @@ func TestDatabaseSql(t *testing.T) {
6666
t.Fatalf("fill failed: %v\n", err)
6767
}
6868

69-
err = retry.DoTx(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
70-
row := tx.QueryRowContext(
71-
ydb.WithQueryMode(ctx, ydb.ExplainQueryMode),
72-
render(
73-
querySelect,
74-
templateConfig{
75-
TablePathPrefix: path.Join(cc.Name(), folder),
76-
},
77-
),
78-
sql.Named("seriesID", uint64(1)),
79-
sql.Named("seasonID", uint64(1)),
80-
sql.Named("episodeID", uint64(1)),
81-
)
82-
var (
83-
ast string
84-
plan string
85-
)
86-
if err = row.Scan(&ast, &plan); err != nil {
87-
return fmt.Errorf("cannot explain: %w", err)
69+
// getting explain of query
70+
row := db.QueryRowContext(
71+
ydb.WithQueryMode(ctx, ydb.ExplainQueryMode),
72+
render(
73+
querySelect,
74+
templateConfig{
75+
TablePathPrefix: path.Join(cc.Name(), folder),
76+
},
77+
),
78+
sql.Named("seriesID", uint64(1)),
79+
sql.Named("seasonID", uint64(1)),
80+
sql.Named("episodeID", uint64(1)),
81+
)
82+
var (
83+
ast string
84+
plan string
85+
)
86+
if err = row.Scan(&ast, &plan); err != nil {
87+
t.Fatalf("cannot explain: %v", err)
88+
}
89+
t.Logf("ast = %v", ast)
90+
t.Logf("plan = %v", plan)
91+
92+
err = retry.DoTx(ctx, db, func(ctx context.Context, tx *sql.Tx) (err error) {
93+
var stmt *sql.Stmt
94+
stmt, err = tx.PrepareContext(ctx, render(
95+
querySelect,
96+
templateConfig{
97+
TablePathPrefix: path.Join(cc.Name(), folder),
98+
},
99+
))
100+
if err != nil {
101+
return fmt.Errorf("cannot prepare query: %w", err)
88102
}
89-
t.Logf("ast = %v", ast)
90-
t.Logf("plan = %v", plan)
91-
row = tx.QueryRowContext(ctx,
92-
render(
93-
querySelect,
94-
templateConfig{
95-
TablePathPrefix: path.Join(cc.Name(), folder),
96-
},
97-
),
103+
104+
row = stmt.QueryRowContext(ctx,
98105
sql.Named("seriesID", uint64(1)),
99106
sql.Named("seasonID", uint64(1)),
100107
sql.Named("episodeID", uint64(1)),

0 commit comments

Comments
 (0)