Skip to content

Commit c31b70f

Browse files
authored
Merge pull request #1109 from ydb-platform/sugar-result
added `sugar.Result` for iterate over `query.Result`
2 parents b518441 + f74c101 commit c31b70f

File tree

5 files changed

+340
-24
lines changed

5 files changed

+340
-24
lines changed

query/named.go

Lines changed: 0 additions & 9 deletions
This file was deleted.

query/result.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,24 @@ type (
1818
NextRow(ctx context.Context) (Row, error)
1919
}
2020
Row interface {
21-
IndexedScanner
22-
NamedScanner
23-
StructScanner
24-
}
25-
IndexedScanner interface {
2621
Scan(dst ...interface{}) error
27-
}
28-
NamedScanner interface {
2922
ScanNamed(dst ...scanner.NamedDestination) error
23+
ScanStruct(dst interface{}, opts ...scanner.ScanStructOption) error
3024
}
31-
StructScanner interface {
32-
ScanStruct(dst interface{}, opts ...scanStructOption) error
33-
}
34-
scanStructOption = scanner.ScanStructOption
3525
)
3626

37-
func WithTagName(name string) scanStructOption {
27+
func Named(columnName string, destinationValueReference interface{}) (dst scanner.NamedDestination) {
28+
return scanner.NamedRef(columnName, destinationValueReference)
29+
}
30+
31+
func WithScanStructTagName(name string) scanner.ScanStructOption {
3832
return scanner.WithTagName(name)
3933
}
4034

41-
func WithAllowMissingColumnsFromSelect() scanStructOption {
35+
func WithScanStructAllowMissingColumnsFromSelect() scanner.ScanStructOption {
4236
return scanner.WithAllowMissingColumnsFromSelect()
4337
}
4438

45-
func WithAllowMissingFieldsInStruct() scanStructOption {
39+
func WithScanStructAllowMissingFieldsInStruct() scanner.ScanStructOption {
4640
return scanner.WithAllowMissingFieldsInStruct()
4741
}

sugar/result.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package sugar
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
8+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/query/scanner"
9+
"github.com/ydb-platform/ydb-go-sdk/v3/query"
10+
"github.com/ydb-platform/ydb-go-sdk/v3/table/result/indexed"
11+
"github.com/ydb-platform/ydb-go-sdk/v3/table/result/named"
12+
)
13+
14+
type result struct {
15+
r query.Result
16+
rs query.ResultSet
17+
row query.Row
18+
}
19+
20+
func (r *result) NextResultSet(ctx context.Context) bool {
21+
var err error
22+
r.rs, err = r.r.NextResultSet(ctx)
23+
if err != nil && errors.Is(err, io.EOF) {
24+
return false
25+
}
26+
27+
return err == nil && r.rs != nil && r.r.Err() == nil
28+
}
29+
30+
func (r *result) NextRow() bool {
31+
if r.rs == nil {
32+
return false
33+
}
34+
35+
var err error
36+
r.row, err = r.rs.NextRow(context.Background())
37+
if err != nil && errors.Is(err, io.EOF) {
38+
return false
39+
}
40+
41+
return r.row != nil && r.r.Err() == nil
42+
}
43+
44+
func (r *result) Scan(indexedValues ...indexed.RequiredOrOptional) error {
45+
values := make([]interface{}, 0, len(indexedValues))
46+
for _, value := range indexedValues {
47+
values = append(values, value)
48+
}
49+
50+
return r.row.Scan(values...)
51+
}
52+
53+
func (r *result) ScanNamed(namedValues ...named.Value) error {
54+
values := make([]scanner.NamedDestination, 0, len(namedValues))
55+
for i := range namedValues {
56+
values = append(values, scanner.NamedRef(namedValues[i].Name, namedValues[i].Value))
57+
}
58+
59+
return r.row.ScanNamed(values...)
60+
}
61+
62+
func (r *result) ScanStruct(dst interface{}) error {
63+
return r.row.ScanStruct(dst)
64+
}
65+
66+
func (r *result) Err() error {
67+
return r.r.Err()
68+
}
69+
70+
func (r *result) Close() error {
71+
return r.r.Close(context.Background())
72+
}
73+
74+
// Result converts query.Result to iterable result for compatibility with table/result.Result usage
75+
//
76+
// # Experimental
77+
//
78+
// Notice: This API is EXPERIMENTAL and may be changed or removed in a later release.
79+
func Result(r query.Result) *result {
80+
return &result{
81+
r: r,
82+
}
83+
}
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package integration
5+
6+
import (
7+
"context"
8+
"os"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/require"
13+
14+
"github.com/ydb-platform/ydb-go-sdk/v3"
15+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/version"
16+
"github.com/ydb-platform/ydb-go-sdk/v3/query"
17+
"github.com/ydb-platform/ydb-go-sdk/v3/sugar"
18+
"github.com/ydb-platform/ydb-go-sdk/v3/table"
19+
"github.com/ydb-platform/ydb-go-sdk/v3/table/result/named"
20+
"github.com/ydb-platform/ydb-go-sdk/v3/table/types"
21+
)
22+
23+
func TestSugarResult(t *testing.T) {
24+
if version.Lt(os.Getenv("YDB_VERSION"), "24.1") {
25+
t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'")
26+
}
27+
28+
ctx, cancel := context.WithCancel(context.Background())
29+
defer cancel()
30+
31+
db, err := ydb.Open(ctx,
32+
os.Getenv("YDB_CONNECTION_STRING"),
33+
ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")),
34+
)
35+
require.NoError(t, err)
36+
t.Run("Scan", func(t *testing.T) {
37+
t.Run("Table", func(t *testing.T) {
38+
var (
39+
p1 string
40+
p2 uint64
41+
p3 time.Duration
42+
)
43+
err = db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) {
44+
_, res, err := s.Execute(ctx, table.DefaultTxControl(), `
45+
DECLARE $p1 AS Text;
46+
DECLARE $p2 AS Uint64;
47+
DECLARE $p3 AS Interval;
48+
SELECT $p1, $p2, $p3;
49+
`,
50+
table.NewQueryParameters(
51+
table.ValueParam("$p1", types.TextValue("test")),
52+
table.ValueParam("$p2", types.Uint64Value(100500000000)),
53+
table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))),
54+
),
55+
)
56+
if err != nil {
57+
return err
58+
}
59+
for res.NextResultSet(ctx) {
60+
for res.NextRow() {
61+
if err = res.Scan(&p1, &p2, &p3); err != nil {
62+
return err
63+
}
64+
}
65+
}
66+
67+
return res.Err()
68+
}, table.WithIdempotent())
69+
require.NoError(t, err)
70+
require.EqualValues(t, "test", p1)
71+
require.EqualValues(t, 100500000000, p2)
72+
require.EqualValues(t, time.Duration(100500000000), p3)
73+
})
74+
t.Run("Sugar", func(t *testing.T) {
75+
var (
76+
p1 string
77+
p2 uint64
78+
p3 time.Duration
79+
)
80+
err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) {
81+
_, r, err := s.Execute(ctx, `
82+
DECLARE $p1 AS Text;
83+
DECLARE $p2 AS Uint64;
84+
DECLARE $p3 AS Interval;
85+
SELECT $p1, $p2, $p3;
86+
`,
87+
query.WithParameters(
88+
table.NewQueryParameters(
89+
table.ValueParam("$p1", types.TextValue("test")),
90+
table.ValueParam("$p2", types.Uint64Value(100500000000)),
91+
table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))),
92+
),
93+
),
94+
)
95+
if err != nil {
96+
return err
97+
}
98+
res := sugar.Result(r)
99+
for res.NextResultSet(ctx) {
100+
for res.NextRow() {
101+
if err = res.Scan(&p1, &p2, &p3); err != nil {
102+
return err
103+
}
104+
}
105+
}
106+
107+
return res.Err()
108+
}, query.WithIdempotent())
109+
require.NoError(t, err)
110+
require.EqualValues(t, "test", p1)
111+
require.EqualValues(t, 100500000000, p2)
112+
require.EqualValues(t, time.Duration(100500000000), p3)
113+
})
114+
})
115+
t.Run("ScanNamed", func(t *testing.T) {
116+
t.Run("Table", func(t *testing.T) {
117+
var (
118+
p1 string
119+
p2 uint64
120+
p3 time.Duration
121+
)
122+
err = db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) {
123+
_, res, err := s.Execute(ctx, table.DefaultTxControl(), `
124+
DECLARE $p1 AS Text;
125+
DECLARE $p2 AS Uint64;
126+
DECLARE $p3 AS Interval;
127+
SELECT $p1 AS p1, $p2 AS p2, $p3 AS p3;
128+
`,
129+
table.NewQueryParameters(
130+
table.ValueParam("$p1", types.TextValue("test")),
131+
table.ValueParam("$p2", types.Uint64Value(100500000000)),
132+
table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))),
133+
),
134+
)
135+
if err != nil {
136+
return err
137+
}
138+
for res.NextResultSet(ctx) {
139+
for res.NextRow() {
140+
if err = res.ScanNamed(
141+
named.Required("p1", &p1),
142+
named.Required("p2", &p2),
143+
named.Required("p3", &p3),
144+
); err != nil {
145+
return err
146+
}
147+
}
148+
}
149+
150+
return res.Err()
151+
}, table.WithIdempotent())
152+
require.NoError(t, err)
153+
require.EqualValues(t, "test", p1)
154+
require.EqualValues(t, 100500000000, p2)
155+
require.EqualValues(t, time.Duration(100500000000), p3)
156+
})
157+
t.Run("Sugar", func(t *testing.T) {
158+
var (
159+
p1 string
160+
p2 uint64
161+
p3 time.Duration
162+
)
163+
err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) {
164+
_, r, err := s.Execute(ctx, `
165+
DECLARE $p1 AS Text;
166+
DECLARE $p2 AS Uint64;
167+
DECLARE $p3 AS Interval;
168+
SELECT $p1 AS p1, $p2 AS p2, $p3 AS p3;
169+
`,
170+
query.WithParameters(
171+
table.NewQueryParameters(
172+
table.ValueParam("$p1", types.TextValue("test")),
173+
table.ValueParam("$p2", types.Uint64Value(100500000000)),
174+
table.ValueParam("$p3", types.IntervalValueFromDuration(time.Duration(100500000000))),
175+
),
176+
),
177+
)
178+
if err != nil {
179+
return err
180+
}
181+
res := sugar.Result(r)
182+
for res.NextResultSet(ctx) {
183+
for res.NextRow() {
184+
if err = res.ScanNamed(
185+
named.Required("p1", &p1),
186+
named.Required("p2", &p2),
187+
named.Required("p3", &p3),
188+
); err != nil {
189+
return err
190+
}
191+
}
192+
}
193+
194+
return res.Err()
195+
}, query.WithIdempotent())
196+
require.NoError(t, err)
197+
require.EqualValues(t, "test", p1)
198+
require.EqualValues(t, 100500000000, p2)
199+
require.EqualValues(t, time.Duration(100500000000), p3)
200+
})
201+
})
202+
t.Run("ScanStruct", func(t *testing.T) {
203+
t.Run("Sugar", func(t *testing.T) {
204+
var data struct {
205+
P1 *string `sql:"p1"`
206+
P2 uint64 `sql:"p2"`
207+
P3 time.Duration `sql:"p3"`
208+
P4 *string `sql:"p4"`
209+
}
210+
err = db.Query().Do(ctx, func(ctx context.Context, s query.Session) (err error) {
211+
_, r, err := s.Execute(ctx, `
212+
DECLARE $p1 AS Text;
213+
DECLARE $p2 AS Uint64;
214+
DECLARE $p3 AS Interval;
215+
SELECT CAST($p1 AS Optional<Text>) AS p1, $p2 AS p2, $p3 AS p3, CAST(NULL AS Optional<Text>) AS p4;
216+
`,
217+
query.WithParameters(
218+
ydb.ParamsBuilder().
219+
Param("$p1").Text("test").
220+
Param("$p2").Uint64(100500000000).
221+
Param("$p3").Interval(time.Duration(100500000000)).
222+
Build(),
223+
),
224+
query.WithSyntax(query.SyntaxYQL),
225+
)
226+
if err != nil {
227+
return err
228+
}
229+
res := sugar.Result(r)
230+
for res.NextResultSet(ctx) {
231+
for res.NextRow() {
232+
if err = res.ScanStruct(&data); err != nil {
233+
return err
234+
}
235+
}
236+
}
237+
238+
return res.Err()
239+
}, query.WithIdempotent())
240+
require.NoError(t, err)
241+
require.NotNil(t, data.P1)
242+
require.EqualValues(t, "test", *data.P1)
243+
require.EqualValues(t, 100500000000, data.P2)
244+
require.EqualValues(t, time.Duration(100500000000), data.P3)
245+
require.Nil(t, data.P4)
246+
})
247+
})
248+
}

tests/slo/native/query/storage.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (s *Storage) Read(ctx context.Context, entryID generator.RowID) (_ generato
110110
return err
111111
}
112112

113-
err = row.ScanStruct(&e, query.WithAllowMissingColumnsFromSelect())
113+
err = row.ScanStruct(&e, query.WithScanStructAllowMissingColumnsFromSelect())
114114
if err != nil {
115115
return err
116116
}

0 commit comments

Comments
 (0)