Skip to content

Commit 4ebada8

Browse files
authored
Merge pull request #1187 from ydb-platform/empty-part
* Fixed out of range panic if next query result set part is empty
2 parents 6d061d5 + 8197947 commit 4ebada8

File tree

3 files changed

+348
-25
lines changed

3 files changed

+348
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Fixed out of range panic if next query result set part is empty
12
* Updated the indirect dependencies `golang.org/x/net` to `v0.17.0` and `golang.org/x/sys` to `v0.13.0` due to vulnerability issue
23

34
## v3.63.0

internal/query/result_set.go

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,39 +48,43 @@ func newResultSet(
4848

4949
func (rs *resultSet) nextRow(ctx context.Context) (*row, error) {
5050
rs.rowIndex++
51-
select {
52-
case <-rs.done:
53-
return nil, io.EOF
54-
case <-ctx.Done():
55-
return nil, xerrors.WithStackTrace(ctx.Err())
56-
default:
57-
if rs.rowIndex == len(rs.currentPart.GetResultSet().GetRows()) {
58-
part, err := rs.recv()
59-
if err != nil {
60-
if xerrors.Is(err, io.EOF) {
61-
close(rs.done)
51+
for {
52+
select {
53+
case <-rs.done:
54+
return nil, io.EOF
55+
case <-ctx.Done():
56+
return nil, xerrors.WithStackTrace(ctx.Err())
57+
default:
58+
if rs.rowIndex == len(rs.currentPart.GetResultSet().GetRows()) {
59+
part, err := rs.recv()
60+
if err != nil {
61+
if xerrors.Is(err, io.EOF) {
62+
close(rs.done)
63+
}
64+
65+
return nil, xerrors.WithStackTrace(err)
6266
}
67+
rs.rowIndex = 0
68+
rs.currentPart = part
69+
if part == nil {
70+
close(rs.done)
6371

64-
return nil, xerrors.WithStackTrace(err)
72+
return nil, xerrors.WithStackTrace(io.EOF)
73+
}
6574
}
66-
rs.rowIndex = 0
67-
rs.currentPart = part
68-
if part == nil {
75+
if rs.index != rs.currentPart.GetResultSetIndex() {
6976
close(rs.done)
7077

71-
return nil, xerrors.WithStackTrace(io.EOF)
78+
return nil, xerrors.WithStackTrace(fmt.Errorf(
79+
"received part with result set index = %d, current result set index = %d: %w",
80+
rs.index, rs.currentPart.GetResultSetIndex(), errWrongResultSetIndex,
81+
))
7282
}
73-
}
74-
if rs.index != rs.currentPart.GetResultSetIndex() {
75-
close(rs.done)
7683

77-
return nil, xerrors.WithStackTrace(fmt.Errorf(
78-
"received part with result set index = %d, current result set index = %d: %w",
79-
rs.index, rs.currentPart.GetResultSetIndex(), errWrongResultSetIndex,
80-
))
84+
if rs.rowIndex < len(rs.currentPart.GetResultSet().GetRows()) {
85+
return newRow(ctx, rs.columns, rs.currentPart.GetResultSet().GetRows()[rs.rowIndex], rs.trace)
86+
}
8187
}
82-
83-
return newRow(ctx, rs.columns, rs.currentPart.GetResultSet().GetRows()[rs.rowIndex], rs.trace)
8488
}
8589
}
8690

internal/query/result_set_test.go

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,324 @@ import (
2020
func TestResultSetNext(t *testing.T) {
2121
ctx := xtest.Context(t)
2222
ctrl := gomock.NewController(t)
23+
t.Run("EmptyResultSet", func(t *testing.T) {
24+
stream := NewMockQueryService_ExecuteQueryClient(ctrl)
25+
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{
26+
Status: Ydb.StatusIds_SUCCESS,
27+
ResultSetIndex: 0,
28+
ResultSet: &Ydb.ResultSet{
29+
Columns: []*Ydb.Column{
30+
{
31+
Name: "a",
32+
Type: &Ydb.Type{
33+
Type: &Ydb.Type_TypeId{
34+
TypeId: Ydb.Type_UINT64,
35+
},
36+
},
37+
},
38+
{
39+
Name: "b",
40+
Type: &Ydb.Type{
41+
Type: &Ydb.Type_TypeId{
42+
TypeId: Ydb.Type_UTF8,
43+
},
44+
},
45+
},
46+
},
47+
Rows: []*Ydb.Value{},
48+
},
49+
}, nil)
50+
stream.EXPECT().Recv().Return(nil, io.EOF)
51+
recv, err := stream.Recv()
52+
require.NoError(t, err)
53+
rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) {
54+
part, err := stream.Recv()
55+
if err != nil {
56+
return nil, xerrors.WithStackTrace(err)
57+
}
58+
59+
return part, nil
60+
}, recv, nil)
61+
require.EqualValues(t, 0, rs.index)
62+
{
63+
_, err := rs.nextRow(ctx)
64+
require.ErrorIs(t, err, io.EOF)
65+
}
66+
})
67+
t.Run("SecondResultSetEmpty", func(t *testing.T) {
68+
stream := NewMockQueryService_ExecuteQueryClient(ctrl)
69+
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{
70+
Status: Ydb.StatusIds_SUCCESS,
71+
ResultSetIndex: 0,
72+
ResultSet: &Ydb.ResultSet{
73+
Columns: []*Ydb.Column{
74+
{
75+
Name: "a",
76+
Type: &Ydb.Type{
77+
Type: &Ydb.Type_TypeId{
78+
TypeId: Ydb.Type_UINT64,
79+
},
80+
},
81+
},
82+
{
83+
Name: "b",
84+
Type: &Ydb.Type{
85+
Type: &Ydb.Type_TypeId{
86+
TypeId: Ydb.Type_UTF8,
87+
},
88+
},
89+
},
90+
},
91+
Rows: []*Ydb.Value{
92+
{
93+
Items: []*Ydb.Value{{
94+
Value: &Ydb.Value_Uint64Value{
95+
Uint64Value: 1,
96+
},
97+
}, {
98+
Value: &Ydb.Value_TextValue{
99+
TextValue: "1",
100+
},
101+
}},
102+
},
103+
{
104+
Items: []*Ydb.Value{{
105+
Value: &Ydb.Value_Uint64Value{
106+
Uint64Value: 2,
107+
},
108+
}, {
109+
Value: &Ydb.Value_TextValue{
110+
TextValue: "2",
111+
},
112+
}},
113+
},
114+
{
115+
Items: []*Ydb.Value{{
116+
Value: &Ydb.Value_Uint64Value{
117+
Uint64Value: 3,
118+
},
119+
}, {
120+
Value: &Ydb.Value_TextValue{
121+
TextValue: "3",
122+
},
123+
}},
124+
},
125+
},
126+
},
127+
}, nil)
128+
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{
129+
Status: Ydb.StatusIds_SUCCESS,
130+
ResultSetIndex: 0,
131+
ResultSet: &Ydb.ResultSet{
132+
Rows: []*Ydb.Value{},
133+
},
134+
}, nil)
135+
stream.EXPECT().Recv().Return(nil, io.EOF)
136+
recv, err := stream.Recv()
137+
require.NoError(t, err)
138+
rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) {
139+
part, err := stream.Recv()
140+
if err != nil {
141+
return nil, xerrors.WithStackTrace(err)
142+
}
143+
144+
return part, nil
145+
}, recv, nil)
146+
require.EqualValues(t, 0, rs.index)
147+
{
148+
_, err := rs.nextRow(ctx)
149+
require.NoError(t, err)
150+
require.EqualValues(t, 0, rs.rowIndex)
151+
}
152+
{
153+
_, err := rs.nextRow(ctx)
154+
require.NoError(t, err)
155+
require.EqualValues(t, 1, rs.rowIndex)
156+
}
157+
{
158+
_, err := rs.nextRow(ctx)
159+
require.NoError(t, err)
160+
require.EqualValues(t, 2, rs.rowIndex)
161+
}
162+
{
163+
_, err := rs.nextRow(ctx)
164+
require.ErrorIs(t, err, io.EOF)
165+
}
166+
})
167+
t.Run("IntermediateResultSetEmpty", func(t *testing.T) {
168+
stream := NewMockQueryService_ExecuteQueryClient(ctrl)
169+
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{
170+
Status: Ydb.StatusIds_SUCCESS,
171+
ResultSetIndex: 0,
172+
ResultSet: &Ydb.ResultSet{
173+
Columns: []*Ydb.Column{
174+
{
175+
Name: "a",
176+
Type: &Ydb.Type{
177+
Type: &Ydb.Type_TypeId{
178+
TypeId: Ydb.Type_UINT64,
179+
},
180+
},
181+
},
182+
{
183+
Name: "b",
184+
Type: &Ydb.Type{
185+
Type: &Ydb.Type_TypeId{
186+
TypeId: Ydb.Type_UTF8,
187+
},
188+
},
189+
},
190+
},
191+
Rows: []*Ydb.Value{
192+
{
193+
Items: []*Ydb.Value{{
194+
Value: &Ydb.Value_Uint64Value{
195+
Uint64Value: 1,
196+
},
197+
}, {
198+
Value: &Ydb.Value_TextValue{
199+
TextValue: "1",
200+
},
201+
}},
202+
},
203+
{
204+
Items: []*Ydb.Value{{
205+
Value: &Ydb.Value_Uint64Value{
206+
Uint64Value: 2,
207+
},
208+
}, {
209+
Value: &Ydb.Value_TextValue{
210+
TextValue: "2",
211+
},
212+
}},
213+
},
214+
{
215+
Items: []*Ydb.Value{{
216+
Value: &Ydb.Value_Uint64Value{
217+
Uint64Value: 3,
218+
},
219+
}, {
220+
Value: &Ydb.Value_TextValue{
221+
TextValue: "3",
222+
},
223+
}},
224+
},
225+
},
226+
},
227+
}, nil)
228+
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{
229+
Status: Ydb.StatusIds_SUCCESS,
230+
ResultSetIndex: 0,
231+
ResultSet: &Ydb.ResultSet{
232+
Rows: []*Ydb.Value{},
233+
},
234+
}, nil)
235+
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{
236+
Status: Ydb.StatusIds_SUCCESS,
237+
ResultSetIndex: 0,
238+
ResultSet: &Ydb.ResultSet{
239+
Columns: []*Ydb.Column{
240+
{
241+
Name: "a",
242+
Type: &Ydb.Type{
243+
Type: &Ydb.Type_TypeId{
244+
TypeId: Ydb.Type_UINT64,
245+
},
246+
},
247+
},
248+
{
249+
Name: "b",
250+
Type: &Ydb.Type{
251+
Type: &Ydb.Type_TypeId{
252+
TypeId: Ydb.Type_UTF8,
253+
},
254+
},
255+
},
256+
},
257+
Rows: []*Ydb.Value{
258+
{
259+
Items: []*Ydb.Value{{
260+
Value: &Ydb.Value_Uint64Value{
261+
Uint64Value: 1,
262+
},
263+
}, {
264+
Value: &Ydb.Value_TextValue{
265+
TextValue: "1",
266+
},
267+
}},
268+
},
269+
{
270+
Items: []*Ydb.Value{{
271+
Value: &Ydb.Value_Uint64Value{
272+
Uint64Value: 2,
273+
},
274+
}, {
275+
Value: &Ydb.Value_TextValue{
276+
TextValue: "2",
277+
},
278+
}},
279+
},
280+
{
281+
Items: []*Ydb.Value{{
282+
Value: &Ydb.Value_Uint64Value{
283+
Uint64Value: 3,
284+
},
285+
}, {
286+
Value: &Ydb.Value_TextValue{
287+
TextValue: "3",
288+
},
289+
}},
290+
},
291+
},
292+
},
293+
}, nil)
294+
stream.EXPECT().Recv().Return(nil, io.EOF)
295+
recv, err := stream.Recv()
296+
require.NoError(t, err)
297+
rs := newResultSet(func() (*Ydb_Query.ExecuteQueryResponsePart, error) {
298+
part, err := stream.Recv()
299+
if err != nil {
300+
return nil, xerrors.WithStackTrace(err)
301+
}
302+
303+
return part, nil
304+
}, recv, nil)
305+
require.EqualValues(t, 0, rs.index)
306+
{
307+
_, err := rs.nextRow(ctx)
308+
require.NoError(t, err)
309+
require.EqualValues(t, 0, rs.rowIndex)
310+
}
311+
{
312+
_, err := rs.nextRow(ctx)
313+
require.NoError(t, err)
314+
require.EqualValues(t, 1, rs.rowIndex)
315+
}
316+
{
317+
_, err := rs.nextRow(ctx)
318+
require.NoError(t, err)
319+
require.EqualValues(t, 2, rs.rowIndex)
320+
}
321+
{
322+
_, err := rs.nextRow(ctx)
323+
require.NoError(t, err)
324+
require.EqualValues(t, 0, rs.rowIndex)
325+
}
326+
{
327+
_, err := rs.nextRow(ctx)
328+
require.NoError(t, err)
329+
require.EqualValues(t, 1, rs.rowIndex)
330+
}
331+
{
332+
_, err := rs.nextRow(ctx)
333+
require.NoError(t, err)
334+
require.EqualValues(t, 2, rs.rowIndex)
335+
}
336+
{
337+
_, err := rs.nextRow(ctx)
338+
require.ErrorIs(t, err, io.EOF)
339+
}
340+
})
23341
t.Run("OverTwoParts", func(t *testing.T) {
24342
stream := NewMockQueryService_ExecuteQueryClient(ctrl)
25343
stream.EXPECT().Recv().Return(&Ydb_Query.ExecuteQueryResponsePart{

0 commit comments

Comments
 (0)